关于一些language的部分性能对比

作者: dplord, 访问量 981





周五晚上闲着无聊跟lys 讨论语言性能

然后我随机一想想出了一个测试用例。

  1. 例子如下:
  2. 创建 10000 个文本,文本在当前目录的 out目录下,文本名为 r_1、r_2、r_3..
  3. 每个文本都有 10000 行,每一行是用 \t  间隔的5个数,每个数是1-10000 之间的随机数

随便我们用什么语言实现,暂时仅仅对比完成目标的时间

其实这个测试用例对比的就是以下几个方面:

  1. 文件IO性能
  2. 文件名需要拼接,字符串concat性能
  3. 大量的循环以及随机数的产生比拼语言的速度

我先顺手写了一个C的,代码如下:

 

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <fcntl.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>

#define MAX 10000

char * one_mil();
int create_one(int filename);
int main()
{
	int i;
	for(i=0;i<MAX;i++)
	{
		create_one(i);
	}
	return 0;
}

int create_one(int filename)
{
	int f;
	char *file_name;
	file_name=malloc(12);
	sprintf(file_name,"out/r_1_%d",filename);
	mode_t f_attr;
	f_attr=S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH;
	f=open(file_name,O_RDWR | O_CREAT,f_attr);
	if(f==-1)
	{
		printf("创建%d出错\n",filename);	
	}else{
		char *temp_b=one_mil();
		write(f,temp_b,strlen(temp_b));
		close(f);
		free(temp_b);
	}
	return 0;
}

char * one_mil()
{
	char *p;
	int i,p_size=0;
	p=malloc(MAX*100);
	char *temp_a;
	temp_a=malloc(100);
	for(i=0;i<10000;i++)
	{
	    sprintf(temp_a,"%d\t%d\t%d\t%d\t%d\n",rand()%10000+1,rand()%10000+1,rand()%10000+1,rand()%10000+1,rand()%10000+1,rand()%10000+1);
	    memcpy(p+p_size,temp_a,strlen(temp_a));
	    p_size+=strlen(temp_a);	
	}	
	free(temp_a);
	return p;
}

编译为:

/home/dengpan/opt/gcc-4.9.1/bin/gcc -std=c99 myfile.c -o a.out -O2

注:后续C代码编译都是最新的gcc 4.9.1 -std=c99 -O2

执行时间为:

20

看来还是很快的,但是还有几个很大的优化点第一个就是频繁申请大块的内存与释放,第二就是sprintf与memcpy执行过多

 

下面来了一个JAVA版本的,几乎默认写法,测试结果惊人

import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.Random;

/**
 * Created by dengpan on 14-10-25.
 */
public class MyFile {
    private static final int MAX = 10000;

    private static final Random sRandom = new Random();

    private static String one_mil() {
        StringBuilder builder = new StringBuilder(MAX * 100);
        for(int i = 0; i < MAX; i++) {
            builder.append(sRandom.nextInt(10000) + 1);
            builder.append('\t');
            builder.append(sRandom.nextInt(10000) + 1);
            builder.append('\t');
            builder.append(sRandom.nextInt(10000) + 1);
            builder.append('\t');
            builder.append(sRandom.nextInt(10000) + 1);
            builder.append('\t');
            builder.append(sRandom.nextInt(10000) + 1);
            builder.append('\n');
        }

        return builder.toString();
    }

    private static void create_one(File dir, int filename) throws IOException {
        File out = new File(dir, "r_java_" + filename);
        if (!out.exists())
            out.createNewFile();
        String content = one_mil();
        FileOutputStream os = new FileOutputStream(out);
        os.write(content.getBytes());
        os.close();
    }

    public static void main(String[] args) {
        File dir = new File("out");
        if (!dir.exists())
            dir.mkdirs();
        for (int i = 0; i < MAX; i++) {
            try {
                create_one(dir, i);
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
}

结果

1

但是多次测试结果不稳定,结果在39s-47s 之间,果然是JVM Snapshot复杂的动态调度有关,不过性能很吓人

因为linux系统所有的对文件读写,内存使用都是系统调用实现的,只不过不同的系统对系统调用有了封装。有了封装必然慢,java简直吓人

我觉得纯粹在文件操作,C用了open write绝对在IO上比JAVA快,只不过在buffer处理上还有sprintf这个函数比java慢抵消了IO优势

 

我决定处理一下刚才的那段C代码

优化方向为:

  • 减少函数调用
  • 少用sprintf把一行5个的sprintf合并
  • 减少内存malloc跟free的次数

处理后的代码为:

#include <string.h>
#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdlib.h>

#define FILE_COUNT 10000

int main()
{
	char filename[100];
	char *text_buf;
	char *p;
	int i, j;
	int fd;
	size_t count = 0;
	int nbytes;

	mode_t f_attr;
	f_attr = S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH | S_IWOTH;

	text_buf = malloc(FILE_COUNT * 100);
	for (i = 0; i < FILE_COUNT; i++) {
		sprintf(filename, "out/r_1_%d", i);		
		fd = open(filename, O_RDWR | O_CREAT, f_attr);
		if (fd < 0) {
			perror("create failed");
			exit(EXIT_FAILURE);
		}

		p = text_buf;
		count = 0;
		for (j = 0; j < FILE_COUNT; j++) {
			nbytes = sprintf(p, "%d\t%d\t%d\t%d\n", 
					rand() % FILE_COUNT + 1,
					rand() % FILE_COUNT + 1,
					rand() % FILE_COUNT + 1,
					rand() % FILE_COUNT + 1,
					rand() % FILE_COUNT + 1);
			if (nbytes <= 0) {
				perror("oome");
				exit(EXIT_FAILURE);
			}

			p += nbytes;
			count += nbytes;
		}
		write(fd, text_buf, count);
		close(fd);
	}
	free(text_buf);
	return 0;
}

编译以后执行

21

优化明显

但是还是比JAVA的慢或者不相上下,按理来说C在这种疯狂调用系统IO绝对比JAVA快,我觉得这段代码的慢在sprintf上,由于我们的格式化仅仅需要格式化%d 都可以自己写一个,于是决定改写sprintf

改写后代码如下:

#include <string.h>
#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdlib.h>

#define FILE_COUNT 10000

size_t sprint_int(char *buf, int val)
{
	size_t nbytes = 0;
	int tmp = val;
	char *p;

	do {
		tmp /= 10;
		nbytes++;
	} while (tmp != 0);
	
	p = buf + nbytes - 1;
	while (p >= buf) {
		tmp = val % 10;
		val /= 10;
		*p-- = '0' + tmp;
	}
	
	return nbytes;
}

int main()
{
	char filename[100];
	char *text_buf;
	char *p;
	int i, j;
	int fd;
	size_t count = 0;
	int nbytes;

	mode_t f_attr;
	f_attr = S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH | S_IWOTH;

	text_buf = malloc(FILE_COUNT * 100);
	for (i = 0; i < FILE_COUNT; i++) {
		sprintf(filename, "out/r_c_%d", i);		
		fd = open(filename, O_RDWR | O_CREAT, f_attr);
		if (fd < 0) {
			perror("create failed");
			exit(EXIT_FAILURE);
		}

		p = text_buf;
		count = 0;
		for (j = 0; j < FILE_COUNT; j++) {
			nbytes = sprint_int(p, rand() % FILE_COUNT + 1);
			count += nbytes + 1;
			p += nbytes;
			*p++ = '\t';

			nbytes = sprint_int(p, rand() % FILE_COUNT + 1);
			count += nbytes + 1;
			p += nbytes;
			*p++ = '\t';

			nbytes = sprint_int(p, rand() % FILE_COUNT + 1);
			count += nbytes + 1;
			p += nbytes;
			*p++ = '\t';

			nbytes = sprint_int(p, rand() % FILE_COUNT + 1);
			count += nbytes + 1;
			p += nbytes;
			*p++ = '\t';

			nbytes = sprint_int(p, rand() % FILE_COUNT + 1);
			count += nbytes + 1;
			p += nbytes;
			*p++ = '\n';

		}
		write(fd, text_buf, count);
		close(fd);
	}
	free(text_buf);
	return 0;
}

结果:

1

果然很不错

本来测试C的,以前学过glib

用glib写了一个版本,看看效果

代码如下:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <fcntl.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <glib.h>

#define MAX_NUM 10000

int main()
{
	mode_t f_attr;
	f_attr = S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH | S_IWOTH;

	for(int i=0; i < MAX_NUM; i++)
	{
		GString *s;
		s = g_string_new("");
		for(int j = 0; j < MAX_NUM; j++)			
		{
			g_string_append_printf(s,"%d",rand() % MAX_NUM + 1);
			g_string_append(s,"\t");
			g_string_append_printf(s,"%d",rand() % MAX_NUM + 1); 
			g_string_append(s,"\t");
			g_string_append_printf(s,"%d",rand() % MAX_NUM + 1); 
			g_string_append(s,"\t");
			g_string_append_printf(s,"%d",rand() % MAX_NUM + 1); 
			g_string_append(s,"\t");
			g_string_append_printf(s,"%d",rand() % MAX_NUM + 1); 
			g_string_append(s,"\n");
		}
		
		char *fname;
		fname = malloc(12);
		sprintf(fname,"out/r_%d",i);
		int f;
		f = open(fname,O_RDWR | O_CREAT,f_attr);
		write(f,s->str,s->len);
		close(f);
		free(fname);
		g_string_free(s,TRUE);
	}
}

结果:

1

看来glib在疯狂的内存释放跟申请方面很弱,所以各大服务器端软件基本都是自己用C实现一套基本数据结构

后面闲着没事都写了一个版本

  • PHP版本,拼接字符串用了
  • concat
  • 拼接成数组,再implode
  • 在github找php StringBuffer的实现

等,最快的如下,下面python ruby  nodejs也仅仅展现测试出来的 最好的版本

<?php
for($i = 0 ; $i < 10000 ; $i++)
{
	$text='';		
	for($j = 0 ; $j < 10000 ; $j++)
	{ 
		$temp='';
		//$text.= rand(1,10000)."\t".rand(1,10000)."\t".rand(1,10000)."\t".rand(1,10000)."\t".rand(1,10000)."\n";
		sprintf($temp,"%d\t%d\t%d\t%d\t%d\n",rand(1,10000),rand(1,10000),rand(1,10000),rand(1,10000),rand(1,10000));
		$text.=$temp;
	}
	file_put_contents("out/r_php_$i",$text);
}

时间

PHP5.6.0  1m51s

HHVM 3.3.0  58s

算是很快很快的

 

python版本的

#encoding:utf-8

import random
for i in xrange(10000):
	f = open('out/r_py_' + str(i) ,'w+')
	a = []
	for j in xrange(10000):
		a.append(random.randint(1,10000))
		a.append('\t')
		a.append(random.randint(1,10000))
		a.append('\t')
		a.append(random.randint(1,10000))
		a.append('\t')
		a.append(random.randint(1,10000))
		a.append('\t')
		a.append(random.randint(1,10000))
		a.append('\n')
	f.write(''.join(a))
	f.close()
	
	

结果 20+ min

ruby版本

#encoding:utf-8
	
require "stringio"

(1..10000).each do |i|
	a = ''
	(1..10000).each do |j|
		s = StringIO.new
		s << rand(1000)+1 << "\t" << rand(1000)+1 << "\t"<< rand(1000)+1 << "\t"<< rand(1000)+1 << "\t"<< rand(1000)+1 << "\n"
	end
	f = File.new("out/r_rb_"+i.to_s,"w+")
	f.syswrite(s.string)
	f.close()
end

结果 20+ min

 

lys写了一个nodejs版本的

var fs = require('fs');
var buf = new Buffer(10000);
var len = 0;
for (var i = 0; i < 10000; i++) {
	for (var j = 0; j < 10000; j++) {
		len += buf.write(Math.floor((Math.random() * 10000) + 1) + "\t", len);
		len += buf.write(Math.floor((Math.random() * 10000) + 1) + "\t", len);
		len += buf.write(Math.floor((Math.random() * 10000) + 1) + "\t", len);
		len += buf.write(Math.floor((Math.random() * 10000) + 1) + "\t", len);
		len += buf.write(Math.floor((Math.random() * 10000) + 1) + "\n", len);
		if (j % 100 == 0) {
			fs.writeFile("out/r_js_" + i, buf.toString('ascii', 0, len), function(err){
				if (err)
					console.log(err);
			});
			len = 0;
		}
	}
}

结果内存溢出跑不完

2015-03-01 更

后来试水学习Rust lang,也用rust lang写了一个版本的, 代码放在github, 也一并放上来吧。当时用的是rust lang快要被废弃的std::old_io , 时间大概耗时3min

https://gist.github.com/dplord/fa02a1cb27f779d70deb

use std::rand;
use std::rand::Rng;

use std::old_io::BufferedReader;
use std::old_io::File;

fn write_file(path: &str, s: &str)
{
	use std::old_io::{BufferedWriter, File};

	let file = File::create(&Path::new(path)).unwrap();
	let mut writer = BufferedWriter::new(file);
	
	writer.write_str(s).unwrap();
	writer.flush().unwrap();
}

fn my_rand() -> i32 {
	let mut rng = rand::thread_rng();
	let n: i32 = rng.gen_range(1, 10000);
	n
}

fn main()
{
	for i in 0..10000
	{
		let mut s = String::with_capacity(262144);
		for j in 0..10000
		{
			let s_one:String = format!("{}\t{}\t{}\t{}\t{}",
				my_rand(),
				my_rand(),
				my_rand(),
				my_rand(),
				my_rand()
			);
			s.push_str(&s_one);
			if(j != 9999) { s.push_str("\n"); }
		}//生成10000行随机数
		//println!("{}\n", i);
						
		let path: String = format!("/tmp/out/r_{}", i);
		write_file(&path, &s);		
	}// 生成10000个文件	
}

 

测试一些小结论

  1. C性能最好
  2. C的一些标准库实现不一定很好比如sprintf这种函数,甚至malloc都不一定实现的好
  3. JAVA经过投入巨资优化之后,现在的JAVA性能已经不是C C++ 可以嘲笑的,部分JAVA 默认的写法性能比C C++ 好,综合性能跟便捷,java是个优秀的语言
  4. C的glib,用在一般还可以,拼性能的时候可以不用
  5. python、ruby、php中,php的官方解释器实现的最好,可以说是惊人的好了,上了HHVM之后更是厉害,期待实现了JIT的php ng, python ruby在部分实现上应该是很差,有待大幅度改进。
  6. nodejs还不是别人吹的那么厉害,我觉得nodejs很适合写socket,其他的cli脚本跟全站式的语言框架 nodejs暂时不太适合

 

  • http://malash.me/ Malash

    学过竞赛的都知道C对于大量文件读写应当用getc、putc

    • dplord

      刚看到 , 我跟我朋友都非竞赛出身。谢指教

  • crabrunning

    cool php is the best language