2017-10-16 140 views
1

使用Erlang的httpc通过HTTP下载文件时,CPU利用率远高于curl或wget。我用来测量下载速度的代码可以在这篇文章的底部看到。下载文件时的CPU利用率高

高CPU利用率问题尤其是在低端设备上。我在一个ARM-SoC上运行Erlang,它比第一个Raspberry PI稍微强大一些,而且这段代码的CPU利用率高达100%,下载速度仅为6.1 MiB/s。使用curl和wget,CPU利用率保持略低于100%,并且几乎完全利用网络接口 (100 MBit/s网络接口上的10.7 MiB/s或85.6 MBit/s)。

我尝试过使用其他HTTP库,包括ibrowse和hackney,但同样的问题仍然存在。我的猜测是它与Erlang的套接字性能有关,但我可能是错的。所以我的问题是,究竟是什么造成了这些缓慢的下载速度,并且是否有任何解决方法?我知道使用libcurl的库如https://github.com/puzza007/katipo,因此可能不会有相同的问题,但我不希望使用任何使用NIF的库。

defmodule DownloadPerformanceTest do 
    @testfile 'http://speed.hetzner.de/100MB.bin' 
    @filesize 104857600 
    @save_to '/dev/null' 

    def test() do 
    Application.start(:inets) 
    then = :erlang.system_time(:micro_seconds) 
    {:ok, :saved_to_file} = :httpc.request(:get, {@testfile, []}, [], [{:stream, @save_to}]) 
    now = :erlang.system_time(:micro_seconds) 
    diff = now - then 
    bw = bandwidth_to_human_readable(@filesize, diff) 
    IO.puts "Download took #{:erlang.trunc(diff/1_000_000)} seconds, average speed: #{bw}" 
    end 

    defp bandwidth_to_human_readable(bytes, microseconds) do 
    bytes_per_second = bytes/(microseconds/1000000) 
    exponent = :erlang.trunc(:math.log2(bytes_per_second)/:math.log2(1024)) 
    prefix = case exponent do 
       0 -> {:ok, ""} 
       1 -> {:ok, "Ki"} 
       2 -> {:ok, "Mi"} 
       3 -> {:ok, "Gi"} 
       4 -> {:ok, "Ti"} 
       5 -> {:ok, "Pi"} 
       6 -> {:ok, "Ei"} 
       7 -> {:ok, "Zi"} 
       8 -> {:ok, "Yi"} 
       _ -> {:error, :too_large} 
      end 
    case prefix do 
     {:ok, prefix} -> 
     quantity = Float.round(bytes_per_second/:math.pow(1024, exponent), 2) 
     unit = "#{prefix}B/s" 
     "#{quantity} #{unit}" 
     {:error, :too_large} -> 
     "#{bytes_per_second} B/s" 
    end 
    end 
end 
+2

你有没有试过这样做只是一个套接字操作?我和httpc一起工作足以发现它有一些特质 - 但我不知道在http下载中会导致高CPU占用率。就我的经验而言,即使在受限的硬件上,Erlang的套接字性能也非常好,但我几乎总是编写纯的套接字进程,而不使用其他协议库,所以我不知道是否有任何低效率的HTTP数据传输正在进行也许HTTP块和标题,而不是以相对昂贵的方式拆卸?)。 – zxq9

+0

@ zxq9确实,使用:gen_tcp直接允许我以9.81 MiB/s的速度进行下载,而使用curl的速度为10.5 MiB/s。我仍然更喜欢使用适当的HTTP库,而不是重新发明轮子,但似乎几乎所有维护良好的库都基于hackney或ibrowse。 – helios35

+0

在Erlang世界中,我们最近才真正开始关注HTTP,实际上,这只是其中的几个地方 - 很多Erlang的工作通常与Web无关,并且也延伸到了文化领域。出于这个原因,我们从来没有真正深入到本地的Erlang网络库或XML解析或疯狂的HTML解析。我想这可能也适用于本地解析HTTP标头,分块等,作为* strings *而不是在二进制文件和原子之间翻转以及二进制到字符串转换等。 – zxq9

回答

1

回过头来看看这个benchmark,三清的问题,我能够探明

  • 您正在使用的是由外部因素使基准数实现远程资源。因此,为了测试,我更改为本地资源
  • 其次,除hackney之外,没有其他库将有效负载传输到文件。虽然保存到/dev/null,但文件保存有成本。
  • 测试需要运行一次以上(可能是三倍)

有一次,我删除了攒动的download_loop_hackney(),哈克尼是最快的

defp download_loop_hackney(client, file) do 
    case :hackney.stream_body(client) do 
     {:ok, _result} -> 

     #IO.binwrite(file, result) 
     download_loop_hackney(client, file) 
     :done -> 
     :ok = File.close(file) 
    end 
    end 

基准数字是这样

download_http: download took 0 seconds, average speed: 211.05 MiB/s 
download_ibrowse: download took 0 seconds, average speed: 223.15 MiB/s 
download_hackney: download took 0 seconds, average speed: 295.83 MiB/s 
download_tcp: download took 0 seconds, average speed: 595.84 MiB/s 
+0

关于保存到'/ dev/null'所带来的开销的好处,虽然它只是不会存储内容的'download_tcp'。根据您获得的下载速度来判断,我认为“本地资源”是指文件是从本地主机下载的。这意味着使用环回设备而不是真实的网络接口。这不是我想测量的,因为高CPU利用率是通过使用网络设备施加的。 – helios35