技术头条 - 一个快速在微博传播文章的方式     搜索本站
您现在的位置首页 --> 编程语言 --> erlang和其他语言读文件性能大比拼

erlang和其他语言读文件性能大比拼

浏览:1098次  出处信息

百岁同学说:

   今天公司技术比武,比赛题目是给一个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写的也是佐证。

建议继续学习:

  1. 海量小文件存储    (阅读:7383)
  2. 其实,文件也可以truncate    (阅读:7257)
  3. 关于Linux的文件系统cache    (阅读:4645)
  4. Erlang match_spec引擎介绍和应用    (阅读:4445)
  5. whatsapp深度使用Erlang有感    (阅读:4419)
  6. Perl 倒行分析文件方法。perl读文本文件,从末尾往前读.    (阅读:4314)
  7. php-erlang    (阅读:4247)
  8. 修改系统最大文件句柄数    (阅读:4122)
  9. PHP:从一个大文件第N行开始读取M行    (阅读:3802)
  10. C/C++循环获取文件中的每行数据(别以为很简单!)    (阅读:3748)
QQ技术交流群:445447336,欢迎加入!
扫一扫订阅我的微信号:IT技术博客大学习
© 2009 - 2024 by blogread.cn 微博:@IT技术博客大学习

京ICP备15002552号-1