我的库中的更改使它变得更慢。分析对我没有帮助。减速的原因可能是什么?

时间:2012-01-31 10:17:10

标签: haskell profiling ghc

我的问题,简要

我对我的图书馆进行了更改,现在它的速度要慢得多,但我无法弄清楚它花费了多少时间。分析报告没有帮助。请帮我弄清楚原因是什么。

一些上下文

我创建了一个名为Hedis的Redis客户端库,并为其创建了一个基准程序。现在,我对库进行了一些内部更改,以清理架构。这导致性能(按所述基准测量的每秒Redis请求数)下降约2.5倍。

基准测试打开与localhost上的Redis服务器的50个网络连接。两个版本之间的连接处理方式不同:

  • 快速版每个连接使用一个线程(因此,基准测试有50个并发运行的线程)。它从套接字读取 使用unsafeInterleaveIO来处理(我在广泛地描述了我的方法) blog post here中的笔画。我有点不高兴 与架构,因此我改变了事情
  • 慢速版,每个连接使用三个线程。它们通过两个Chan进行通信(在基准测试中运行150个线程)。

可能相关的更多信息:

  • 编译GHC 7.2.2。
  • 基准程序在两个版本之间没有变化,所以网络 交通是一样的。
  • 两个版本都使用单线程运行时(没有编译-threaded)。
  • 通过调用forkIO创建所有线程。 越贵 forkOS

分析结果

剖析并没有给我一个明显的理由来说明性能下降。根据分析报告,两个版本在System.IO.hFlushData.ByteString.hGetSome上花费的时间超过99%。在两个版本中调用hFlushhGetSome的次数相同。由于两种情况下网络流量也相同,因此这些功能不能成为减速的原因。

我可以在两个版本之间衡量的唯一显着差异是time(Unix实用程序)告诉我的:慢版本(线程数是其三倍)在“sys”中花费的时间明显多于与快速版相比,“用户”。 GHC +RTS -s标志将此报告为降低了生产力。

以下是具有+RTS -s标志的两个版本的程序输出:

快速版的基准

$ time ./dist/build/hedis-benchmark/hedis-benchmark +RTS -s -p
ping                   33305.29 Req/s
get                    25802.92 Req/s
mget                   18215.94 Req/s
ping (pipelined)      268994.36 Req/s
   5,118,163,904 bytes allocated in the heap
     185,075,608 bytes copied during GC
       4,084,384 bytes maximum residency (39 sample(s))
         916,544 bytes maximum slop
              10 MB total memory in use (0 MB lost due to fragmentation)

                                    Tot time (elapsed)  Avg pause  Max pause
  Gen  0      7416 colls,     0 par    0.38s    0.40s     0.0001s    0.0003s
  Gen  1        39 colls,     0 par    0.03s    0.03s     0.0007s    0.0009s

  INIT    time    0.00s  (  0.00s elapsed)
  MUT     time    7.93s  ( 12.34s elapsed)
  GC      time    0.41s  (  0.43s elapsed)
  RP      time    0.00s  (  0.00s elapsed)
  PROF    time    0.00s  (  0.00s elapsed)
  EXIT    time    0.00s  (  0.00s elapsed)
  Total   time    8.33s  ( 12.76s elapsed)

  %GC     time       4.9%  (3.3% elapsed)

  Alloc rate    645,587,554 bytes per MUT second

  Productivity  95.1% of total user, 62.1% of total elapsed


real    0m12.772s
user    0m8.334s
sys     0m4.424s

慢速版的基准

$ time ./dist/build/hedis-benchmark/hedis-benchmark +RTS -s -p
ping                   11457.83 Req/s
get                    11169.64 Req/s
mget                    8446.96 Req/s
ping (pipelined)      130114.31 Req/s
   6,053,055,680 bytes allocated in the heap
   1,184,574,408 bytes copied during GC
       9,750,264 bytes maximum residency (198 sample(s))
       2,872,280 bytes maximum slop
              26 MB total memory in use (0 MB lost due to fragmentation)

                                    Tot time (elapsed)  Avg pause  Max pause
  Gen  0      9105 colls,     0 par    2.11s    2.14s     0.0002s    0.0006s
  Gen  1       198 colls,     0 par    0.23s    0.24s     0.0012s    0.0093s

  INIT    time    0.00s  (  0.00s elapsed)
  MUT     time   10.99s  ( 27.92s elapsed)
  GC      time    2.34s  (  2.38s elapsed)
  RP      time    0.00s  (  0.00s elapsed)
  PROF    time    0.00s  (  0.00s elapsed)
  EXIT    time    0.00s  (  0.00s elapsed)
  Total   time   13.33s  ( 30.30s elapsed)

  %GC     time      17.6%  (7.8% elapsed)

  Alloc rate    550,656,490 bytes per MUT second

  Productivity  82.4% of total user, 36.3% of total elapsed


real    0m30.305s
user    0m13.333s
sys     0m16.964s

您是否有任何想法或提示可能会产生额外的时间?

2 个答案:

答案 0 :(得分:3)

根据分析报告,大部分时间都花在hFlushhGetSome上。根据{{​​1}},慢版本需要更多的系统时间。因此,我的假设是,无论是等待更多输入还是锁定和解锁线程,都会花费大量时间来阻止和等待。

这是我要做的第一件事:用time编译代码,看看会发生什么。线程运行时使用完全不同的IO管理器,我强烈怀疑这一单一更改将解决您的问题。

答案 1 :(得分:2)

我的猜测会与Chan的开销有关。

我的第一个想法是增加了GC的时间,但这似乎并非如此。所以我的第二个想法是,使用Chan(在MVar之上实现)所涉及的所有锁定和解锁都可能是问题所在。但这仍然是一个猜测。

您可以尝试TChan(即STM),看看是否会产生丝毫差异。 (也许你可以编写一个小骨架来比较两者,看看问题出在哪里,而不是重新实现你的“真实”代码。)

除此之外,我没有想法。