周五晚上闲着无聊跟lys 讨论语言性能
然后我随机一想想出了一个测试用例。
- 例子如下:
- 创建 10000 个文本,文本在当前目录的 out目录下,文本名为 r_1、r_2、r_3..
- 每个文本都有 10000 行,每一行是用 \t 间隔的5个数,每个数是1-10000 之间的随机数
随便我们用什么语言实现,暂时仅仅对比完成目标的时间
其实这个测试用例对比的就是以下几个方面:
- 文件IO性能
- 文件名需要拼接,字符串concat性能
- 大量的循环以及随机数的产生比拼语言的速度
我先顺手写了一个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
执行时间为:
看来还是很快的,但是还有几个很大的优化点第一个就是频繁申请大块的内存与释放,第二就是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(); } } } }
结果
但是多次测试结果不稳定,结果在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; }
编译以后执行
优化明显
但是还是比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; }
结果:
果然很不错
本来测试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); } }
结果:
看来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; } } }
结果内存溢出跑不完