erlang和其他语言读文件性能大比拼
百岁同学说:
今天公司技术比武,比赛题目是给一个1.1g的大文本,统计文本中词频最高的前十个词。花了两天用erlang写完了代码,但是放到公司16核的机器上这么一跑,结果不比不知道,一比吓一条。erlang写的代码执行时间花了55秒左右,同事们有的用java,有的用C,还有的用C++,用C最快一个老兄只花了2.6秒,用java的也只用了3.2秒。相比之下erlang的代码,真是一头大蜗牛,太慢了。
详细参见这篇文章:http://www.iteye.com/topic/1131748
读取文件并且分析这是很多脚本语言如perl, python,ruby经常会干的事情.这个同学的问题是很普遍的问题,不只一个人反映过慢的问题。
今天我们来重新来修正下这个看法, 我们用数据说话。
首先我们来准备下文件, 这个文件是完全的随机数,有1G大小:
$ dd if=/dev/urandom of=test.dat count=1024 bs=1024K 1024+0 records in 1024+0 records out 1073741824 bytes (1.1 GB) copied, 188.474 s, 5.7 MB/s $ time dd if=test.dat of=/dev/null 2097152+0 records in 2097152+0 records out 1073741824 bytes (1.1 GB) copied, 1.16021 s, 925 MB/s real 0m1.162s user 0m0.219s sys 0m0.941s $ time dd if=test.dat of=/dev/null bs=1024k 1024+0 records in 1024+0 records out 1073741824 bytes (1.1 GB) copied, 0.264298 s, 4.1 GB/s real 0m0.266s user 0m0.000s sys 0m0.267s
我们准备了1G大小左右的文件,由于用的是buffered io, 数据在准备好了后,全部缓存在pagecache里面,只要内存足够,这个测试的性能和IO设备无关。 我们试着用dd读取这个文件,如果块大小是4K的话,读取这个文件花了1.16秒,而如果块大小是1M的话,0.26秒,带宽达到4.1GB每秒,远超过真实设备的速度。
那么我们用erlang来读取下这个文件来比较下,我们有三种读法:
1. 一下子读取整个1G文件。
2. 一个线程一次读取1块,比如1M大小,直到读完。
3. 多个线程读取,每个读取一大段,每次读取1M块大小。
然后比较下性能。
首先普及下背景:
1. erlang的文件IO操作由efile driver来提高,这个driver内部有个线程池,大小由+A 参数控制,所以IO是多线程完成的。
2. erlang的文件分二种模式: 1. raw模式 2. io模式 在raw模式下,数据直接由driver提供给调用进程, io模式下数据先经过file_server做格式化,然后再给调用进程。
3. 数据可以以binary和list方式返回,list方式下文件内容的byte就是一个整数,在64位机器上占用8个字节内存。
我们编写程序的时候要注意上面几点:
$ cat >rf.erl -module(rf). -compile(export_all). -include_lib("kernel/include/file.hrl"). read(Filename)-> read(Filename, 1024 * 1024). read(File, Bs)-> case file:open(File, [raw, binary]) of {ok, Fd} -> scan_file(Fd, file:read(Fd, Bs), Bs); {error, _} = E -> E end . scan_file(Fd, {ok, _Binary}, Bs) -> scan_file(Fd, file:read(Fd, Bs), Bs); scan_file(Fd, eof, _Bs) -> file:close(Fd); scan_file(Fd, {error, _}, _Bs) -> file:close(Fd). read1(Filename) -> {ok, _Binary} = file:read_file(Filename), ok. upmap(F, L) -> Parent = self(), Ref = make_ref(), [receive {Ref, Result} -> Result end || _ <- [spawn(fun() -> Parent ! {Ref, F(X)} end) || X <- L]]. read2(Filename)-> PoolSize = erlang:system_info(thread_pool_size), read2(Filename, PoolSize). read2(_, 0)-> io:format("setting +A first"); read2(Filename, PoolSize)-> {ok, FInfo} = file:read_file_info(Filename), Bs = FInfo#file_info.size div PoolSize, erlang:display([{bs, Bs}, {poolsize, PoolSize}]), upmap(fun (Off)-> {ok, Fd} = file:open(Filename, [raw, binary]), {ok, _} = file:pread(Fd, Off * Bs, Bs), file:close(Fd), erlang:display([reading, block, Off * Bs, Bs, done]), ok end, lists:seq(0, PoolSize - 1)), ok. CTRL+D $ erlc rf.erl
我们导出了三个读,分别对应着上面的3种方式,代码read,read1由帖子的作者的代码稍微修改来的,read2是我自己写的。
我们来测试下文件读取具体的性能:
$ erl +A 16 Erlang R15B03 (erts-5.9.3.1) 1 [64-bit] [smp:16:16] [async-threads:16] [hipe] [kernel-poll:false] Eshell V5.9.3.1 (abort with ^G) 1> timer:tc(rf, read, ["test.dat"]). {322366,ok} 2> timer:tc(rf, read1, ["test.dat"]). {779240,ok} 3> timer:tc(rf, read2, ["test.dat"]). [{bs,67108864},{poolsize,16}] [reading,block,603979776,67108864,done] [reading,block,402653184,67108864,done] [reading,block,939524096,67108864,done] [reading,block,0,67108864,done] [reading,block,805306368,67108864,done] [reading,block,268435456,67108864,done] [reading,block,469762048,67108864,done] [reading,block,1006632960,67108864,done] [reading,block,671088640,67108864,done] [reading,block,134217728,67108864,done] [reading,block,335544320,67108864,done] [reading,block,67108864,67108864,done] [reading,block,872415232,67108864,done] [reading,block,738197504,67108864,done] [reading,block,536870912,67108864,done] [reading,block,201326592,67108864,done] {214904,ok} 4> timer:tc(rf, read, ["test.dat", 1024]). {19826104,ok} 5> timer:tc(rf, read, ["test.dat", 4096]). {3563313,ok}
我们采用的块大小是1M, 三种模式下对应的读取时间分别是0.322, 0.779, 0.214s,相比dd的0.264s, 我们可以看到多线程模式比c还快,单线程一次读和c差不多。带宽达到4-5G,是理论值的极限,也证明我们把这个事情做到极致了。
同时我们也看到了,如果1次读1K的话,就悲剧了,19秒,很多人会犯的错误。
同样的以4K块读,dd花了1.16秒,而Erlang花了3.56秒, 读完文件循环的次数是1G/4K=25万次。 任何细小的差别放大25万次都会很明显。
结论是: erlang的io是薄薄的一层c封装,每个file:read或者pread的时候,会把读写的具体参数发给driver, 然后等待driver发消息,返回IO结果。 每个io操作会涉及: 发消息+driver做IO操作+等消息 三个阶段。 所以如果我们的io操作太小,发消息和等消息的代价就会大,违反erlang的”小消息,大计算”的设计理念,低性能是一定的。
每个语言都有自己的特点,erlang同样有自己的惯用法。在io上,erlang性能是很高的,那么多的数据库系统是erlang写的也是佐证。
建议继续学习:
- 海量小文件存储 (阅读:7620)
- 其实,文件也可以truncate (阅读:7455)
- 关于Linux的文件系统cache (阅读:4848)
- whatsapp深度使用Erlang有感 (阅读:4633)
- Erlang match_spec引擎介绍和应用 (阅读:4576)
- Perl 倒行分析文件方法。perl读文本文件,从末尾往前读. (阅读:4497)
- php-erlang (阅读:4319)
- 修改系统最大文件句柄数 (阅读:4334)
- PHP:从一个大文件第N行开始读取M行 (阅读:3994)
- linux file命令是如何识别文件的类型的 (阅读:4029)
扫一扫订阅我的微信号:IT技术博客大学习
- 作者:Yu Feng 来源: 系统技术非业余研究
- 标签: erlang 文件
- 发布时间:2013-08-29 13:28:42
- [69] Twitter/微博客的学习摘要
- [67] IOS安全–浅谈关于IOS加固的几种方法
- [65] 如何拿下简短的域名
- [65] android 开发入门
- [63] find命令的一点注意事项
- [62] Go Reflect 性能
- [61] 流程管理与用户研究
- [60] Oracle MTS模式下 进程地址与会话信
- [59] 图书馆的世界纪录
- [57] 读书笔记-壹百度:百度十年千倍的29条法则