【问题标题】:Common Lisp Compiling and execution timeCommon Lisp 编译和执行时间
【发布时间】:2012-03-05 03:00:16
【问题描述】:

我有一个 lisp 文件,它在循环中执行大量采样、文件 I/O 和算术运算。 (我在普通的 lisp 中使用 particle filtering。)我正在使用 compile-file 命令编译我的 lisp 文件。我还在 lisp 文件的开头使用 (declaim (optimize (speed 3) (debug 0) (safety 0))),因为我希望尽快获得结果。
我使用(time (load "/....../myfile.lisp")(time (load "/......./myfile.dx64fsl") 来测量速度。问题是编译不会给我带来任何好处。没有任何改善。我做错了什么吗?有没有办法改善事情?速度是最重要的标准,所以我可以牺牲很多来获得快速响应。我不知道这类问题,所以任何帮助将不胜感激。
此外,当我将粒子的数量(每个粒子是一个大小约为 40 的向量)增加到 10000 时,代码变得非常慢,因此也可能存在一些内存问题。
非常感谢您。

编辑:这是 1000 个粒子和 50 次迭代的分析结果。

(LOAD "/.../myfile.dx64fsl") took 77,488,810 microseconds (77.488810 seconds) to run 
                    with 8 available CPU cores.
During that period, 44,925,468 microseconds (44.925470 seconds) were spent in user mode
                    32,005,440 microseconds (32.005440 seconds) were spent in system mode
2,475,291 microseconds (2.475291 seconds) was spent in GC.
 1,701,028,429 bytes of memory allocated.
 1,974 minor page faults, 0 major page faults, 0 swaps.
; Warning: Function CREATE-MY-DBN has been redefined, so times may be inaccurate.
;          MONITOR it again to record calls to the new definition.
; While executing: MONITOR::MONITOR-INFO-VALUES, in process repl-thread(10).


                                                               Cons   
                             %      %                          Per      Total     Total
Function                    Time   Cons    Calls  Sec/Call     Call     Time      Cons
------------------------------------------------------------------------------------------
SAMPLE:                    25.61   26.14  2550000  0.000005      174    13.526   443040000
DISCRETE-PARENTS:          19.66    3.12  4896000  0.000002       11    10.384    52800000
LINEAR-GAUSSIAN-MEAN:       8.86    3.12  1632000  0.000003       32     4.679    52800000
DISCRETE-PARENT-VALUES:     7.47   12.33  3264000  0.000001       64     3.946   208896000
LIST-DIFFERENCE:            6.41   25.69  6528000  0.000001       67     3.384   435392000
CONTINUOUS-PARENTS:         6.33    0.00  1632000  0.000002        0     3.343           0
PF-STEP:                    5.17    0.23       48  0.056851    80080     2.729     3843840
CONTINUOUS-PARENT-VALUES:   4.13    7.20  1632000  0.000001       75     2.184   122048000
TABLE-LOOKUP:               3.85    8.39  2197000  0.000001       65     2.035   142128000
PHI-INVERSE:                3.36    0.00  1479000  0.000001        0     1.777           0
PHI-INTEGRAL:               3.32    1.38  2958000  0.000001        8     1.755    23344000
PARENT-VALUES:              2.38   10.65  1122000  0.000001      161     1.259   180528016
CONDITIONAL-PROBABILITY:    1.41    0.00   255000  0.000003        0     0.746           0
------------------------------------------------------------------------------------------
TOTAL:                     97.96   98.24  30145048                       51.746  1664819856
Estimated monitoring overhead: 21.11 seconds
Estimated total monitoring overhead: 23.93 seconds

有 10000 个粒子和 50 次迭代:

(LOAD "/.../myfile.dx64fsl") took 809,931,702 microseconds (809.931700 seconds) to run 
                    with 8 available CPU cores.
During that period, 476,627,937 microseconds (476.627930 seconds) were spent in user mode
                    328,716,555 microseconds (328.716550 seconds) were spent in system mode
54,274,625 microseconds (54.274624 seconds) was spent in GC.
 16,973,590,588 bytes of memory allocated.
 10,447 minor page faults, 417 major page faults, 0 swaps.
; Warning: Funtion CREATE-MY-DBN has been redefined, so times may be inaccurate.
;          MONITOR it again to record calls to the new definition.
; While executing: MONITOR::MONITOR-INFO-VALUES, in process repl-thread(10).


                                                               Cons    
                             %      %                          Per       Total     Total
Function                    Time   Cons    Calls  Sec/Call     Call      Time      Cons
-------------------------------------------------------------------------------------------
SAMPLE:                    25.48   26.11  25500000  0.000006       174   144.211  4430400000
DISCRETE-PARENTS:          18.41    3.11  48960000  0.000002        11   104.179   528000000
LINEAR-GAUSSIAN-MEAN:       8.61    3.11  16320000  0.000003        32    48.751   528000000
LIST-DIFFERENCE:            7.57   25.66  65280000  0.000001        67    42.823  4353920000
DISCRETE-PARENT-VALUES:     7.50   12.31  32640000  0.000001        64    42.456  2088960000
CONTINUOUS-PARENTS:         5.83    0.00  16320000  0.000002         0    32.980           0
PF-STEP:                    5.05    0.23       48  0.595564    800080    28.587    38403840
TABLE-LOOKUP:               4.52    8.38  21970000  0.000001        65    25.608  1421280000
CONTINUOUS-PARENT-VALUES:   4.25    7.19  16320000  0.000001        75    24.041  1220480000
PHI-INTEGRAL:               3.15    1.38  29580000  0.000001         8    17.849   233440000
PHI-INVERSE:                3.12    0.00  14790000  0.000001         0    17.641           0
PARENT-VALUES:              2.87   10.64  11220000  0.000001       161    16.246  1805280000
CONDITIONAL-PROBABILITY:    1.36    0.00  2550000  0.000003         0     7.682           0
-------------------------------------------------------------------------------------------
TOTAL:                     97.71   98.12  301450048                       553.053  16648163840
Estimated monitoring overhead: 211.08 seconds
Estimated total monitoring overhead: 239.13 seconds

【问题讨论】:

  • 那么,您的文件不仅包含函数定义吗?即,它实际上执行了其中一个功能,对吗?
  • 我实际上在某个目录中定义了很多函数。我先加载它。然后在我的文件中,在一个循环中,我调用定义的函数之一来获得结果,即我拥有的查询变量的均值和方差。我在开始时使用其他函数来构建我的贝叶斯网络,然后我每次在循环中调用一个函数来进行推理。
  • 你应该编译那些其他文件吗?
  • 我也编译了其他文件。虽然我不确定这是否有必要。
  • 这两个测试的唯一区别是,一个是加载.lisp,另一个是加载.dx64fsl?如果是这种情况,lisp 会话可能会即时编译每个功能表单(在 .lisp 场景中),因此加载 .lisp 文件和加载 .dx64fsl 文件之间的唯一时间差就是编译时间;我假设在您的场景中几乎为零。

标签: compilation lisp common-lisp


【解决方案1】:

Common Lisp 中的典型算术运算可能很慢。改进它是可能的,但需要一些知识。

原因:

  • 普通的 Lisp 数字不是机器提供的(bignums、rational、complex 等)
  • 自动从 fixnum 更改为 bignum 并返回
  • 通用数学运算
  • 标记使用字大小的位
  • 数字组合

您可以从分析输出中看到的一件事是您生成了 1.7 GB 的垃圾。这是您的数字操作缺点的典型提示。摆脱它往往不是那么容易。这只是我的猜测,这些是数字运算。

Ken Anderson(不幸的是,他几年前去世了)在他的网站上提出了一些改进数字软件的建议:http://openmap.bbn.com/~kanderso/performance/

通常的解决方案是将代码提供给一些对所使用的编译器和/或优化有一定了解的有经验的 Lisp 开发人员。

【讨论】:

    【解决方案2】:

    首先,永远不要永远在顶级(即全局)声明(speed 3)(safety 0)。这迟早会回来咬掉你的头。在这些级别上,大多数常见的 lisp 编译器进行的安全检查比 C 编译器少。例如,一些 lisps 放弃检查(safety 0) 代码中的中断信号。接下来,(safety 0) 很少有明显的收益。我会在热门函数中声明(speed 3)(safety 1)(debug 1),如果这会带来明显的收益,可能会去(debug 0)

    否则,如果不实际查看一些实际代码,就很难提出建议。从 time() 看来,GC 压力似乎有点高。确保在热函数中使用开放编码算法,并且不要不必要地装箱浮点数或整数。使用(disassemble 'my-expensive-function) 仔细查看编译器生成的代码。 SBCL 将在以速度为优先级进行编译时提供大量有用的输出,尝试消除其中一些警告是值得的。

    使用快速数据结构来表示粒子也很重要,如果需要,可以使用可实例化的数组和宏观。

    【讨论】:

      【解决方案3】:

      如果“myfile.lisp”中包含的所有代码都是您进行计算的部分,不,编译该文件不会显着改善您的运行时间。这两种情况的区别可能是“我们编译了几个循环”,调用了在这两种情况下编译或解释的函数。

      要从编译中获得改进,您还需要编译被调用的代码。您可能还需要对代码进行类型注释,以使您的编译器能够更好地优化。 SBCL 对遗漏的注释有很好的编译器诊断(以至于人们抱怨它在编译时过于冗长)。

      就加载时间而言,实际上可能是加载编译文件需要更长的时间(如果您不经常更改代码,但会发生大量动态链接您处理的数据,使用您的粒子过滤代码准备一个新的核心文件可能是一个优势)。

      【讨论】:

        【解决方案4】:

        几点:

        1. 如果可能,尝试将文件 I/O 移出循环;迭代前将数据批量读入内存。文件 I/O 比内存访问慢几个数量级。

        2. 如果执行速度对您很重要,请尝试SBCL

        3. 输入增加十倍会导致执行时间增加大约十倍,这是线性的,因此您的算法看起来不错;只需要在你的常数因子上工作。

        4. 利用 Lisp 工作流程:编辑函数、编译函数和测试运行,而不是编辑文件、编译文件和测试。当您的项目变得更大时(或者当您尝试使用 SBCL,分析/优化您的程序以生成更快的代码需要更长的时间)时,差异会变得很明显。

        【讨论】:

        • 我将文件 I/O 替换为将输出分配给向量。然而,它并没有改变任何实质性的东西。如果您可以查看上面的分析,您会发现大部分时间都被samplepf-step 占用。我可能会尝试并行化sample。如果您有任何进一步的建议,我想听听他们的意见。非常感谢您的回答。
        【解决方案5】:
        Welcome to Clozure Common Lisp Version 1.7-r14925M  (DarwinX8664)!
        ? (inspect 'print)
        [0]     PRINT
        [1]     Type: SYMBOL
        [2]     Class: #<BUILT-IN-CLASS SYMBOL>
                Function
        [3]     EXTERNAL in package: #<Package "COMMON-LISP">
        [4]     Print name: "PRINT"
        [5]     Value: #<Unbound>
        [6]     Function: #<Compiled-function PRINT #x30000011C9DF>
        [7]     Arglist: (CCL::OBJECT &OPTIONAL STREAM)
        [8]     Plist: NIL
        
        Inspect> (defun test (x) (+ x 1))
        TEST
        Inspect> (inspect 'test)
        [0]     TEST
        [1]     Type: SYMBOL
        [2]     Class: #<BUILT-IN-CLASS SYMBOL>
                Function
        [3]     INTERNAL in package: #<Package "COMMON-LISP-USER">
        [4]     Print name: "TEST"
        [5]     Value: #<Unbound>
        [6]     Function: #<Compiled-function TEST #x302000C5EFFF>
        [7]     Arglist: (X)
        [8]     Plist: NIL
        Inspect> 
        

        请注意,#'print 和 #'test 都被列为“已编译”。这意味着加载 .lisp 文件和加载编译文件之间的唯一性能差异是编译时间。我假设这不是您场景中的瓶颈。通常不会,除非您使用一堆宏,并且执行代码转换是您程序的主要目的。

        这是我从不处理已编译的 lisp 文件的主要原因之一。我只是在我的核心文件中加载我需要的所有共享库/包,然后在我处理特定项目时加载一些特定的 .lisp 函数/文件。而且,至少对于我来说,SBCL 和 CCL 的所有内容都被列为“已编译”。

        【讨论】:

        • 其实就是这样。我的 .dx64fsl 文件导致的时间比 .lisp 版本多一点。那么这个区别就是编译时间。你对让我的代码更快有什么想法吗?如果您能分享一些建议,我将不胜感激。
        • 是否有任何函数被反复调用,具有相同的输入,一遍又一遍?如果是这样,您可以记住这些功能。我以前用过 Norvig 的 defun-memo(自动备忘录)。
        • pf-step 在每次迭代的循环中被调用。它使用上一次迭代的结果来更新自身。但是,它会调用各种其他函数,例如 samplelinear-gaussian-mean,它们只是从分布中随机抽样。他们会查看特定节点的父节点的值,并根据这些值生成样本。
        • 你可以生成一个线程来生成一堆样本和线性高斯平均值,并将它们缓存起来。然后当线程请求它时,它会向主线程提供一个结果。这是假设这些抽样计算成本很高。所以有一个线程负责采样,还有一个线程负责使用该数据。我会说,在我花了很多时间让单线程案例快速运行之前,我不会诉诸线程。实际上,我从未在 lisp 中使用过任何高级的细粒度并行化,所以不要过多地听我的线程建议。
        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 2012-07-31
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2021-03-25
        • 2014-02-20
        相关资源
        最近更新 更多