2016-01-14 57 views
4

Common Lisp的最佳做法,我有合并两个符号有序列表一个Common Lisp的功能,无需重复(两个有序集):使用类型声明进行优化

(defun my-merge (x y) 
    "merge two lists of symbols *already sorted and without duplicates* 
    (and return the resulting list sorted and without duplicates)" 
    (let* ((first (cons nil nil)) 
     (last first)) 
    (loop while (and x y) 
     for cx = (car x) 
     for cy = (car y) 
     if (string= cx cy) 
     do (setf x (cdr x)) 
     else if (string< cx cy) 
     do (rplacd last (cons cx nil)) 
     and do (setf last (cdr last) 
        x (cdr x)) 
     else do (rplacd last (cons cy nil)) 
     and do (setf last (cdr last) 
        y (cdr y))) 
    (rplacd last (or x y)) 
    (cdr first))) 

既然我已经发现了大约只有很少的信息为了有效地编译代码的使用实际情况类型声明的,我不能确定它是否足够以这种方式声明变量,例如:

(defun my-merge (x y) 
    "merge two list of symbols *already sorted and without duplicates*" 
    (declare (list x y)) 
    (let* ((first (cons nil nil)) 
     (last first)) 
    (declare (cons first last)) 
    (loop while (and x y) 
     for cx symbol = (car x) 
     for cy symbol = (car y) 
     ... 

,或者因为我想,如果是还需要添加the说明符到我的代码?但那么,其中哪些情况下应该加吗?

有一些规则可以遵循?

我是否还应该为我的优化目的声明我的函数的类型?

+2

优化,使用典型值的优化和类型推断的声明完全是特定于实现的。你会在实现中找到所有的东西:从基于类型声明的零优化,使用类型声明,甚至是*类型推断*(这意味着需要更少的类型声明)。通常:声明类型并进行优化,然后查看生成的汇编程序和/或测量时间。如果你使用SBCL,CMUCL或者LispWorks,编译器会(或者可以配置为)告诉你它不能优化的地方以及为什么。然后会添加必要的声明。 –

+0

推出自己的解决方案总是很有趣,但请注意,最好的选择之一可能就是使用标准[** merge **](http://www.lispworks.com/documentation/HyperSpec/Body/) f_merge.htm)函数。它可以产生任意的序列类型,采用任意的谓词等,并且实现可能已经优化了它(可能值得查看它们的来源)。 –

+0

@JoshuaTaylor,这是我的第一选择,但后来我测试了SBCL和CCL的合并函数,它们是我目前使用的实现,并且* both *在大型列表上的性能明显最差(不记得确切的数字)。我也不知道其中的原因,或者我在执行测试时做了错误。 – Renzo

回答

8

风格

既然你不实际使用扩展LOOP功能以任何有用的方式和LOOP语法是不是你的榜样,伟大的,我建议把它与原始LOOP写。了解如何COND使其更具可读性的Lisp的程序员:

(defun my-merge (x y &aux (first (list nil)) (last first) cx cy) 
    (macrolet ((cdr! (v) 
       `(setf ,v (cdr ,v)))) 
    (loop (unless (and x y) 
      (return)) 
      (setf cx (car x) cy (car y)) 
      (cond ((string= cx cy) 
       (cdr! x)) 
       ((string< cx cy) 
       (rplacd last (list cx)) 
       (cdr! last) 
       (cdr! x)) 
       (t 
       (rplacd last (list cy)) 
       (cdr! last) 
       (cdr! y)))) 
    (rplacd last (or x y)) 
    (cdr first))) 

编译

由于编译器的复杂程度:

  • 完全傻=编译器会忽略所有声明 - >声明没有帮助

  • 大多是愚蠢的=编译器ne编辑声明无处不在,但优化 - >你需要编写大量声明

例如:

(let ((a 1) (b 2)) 
    (declare (integer a b)) 
    (let ((c (the integer (* (the integer (+ a b)) 
          (the integer (- a b)))))) 
    (declare (integer c)) 
    (the integer (* c c)))) 

注意,这可能还不够了解的参数类型是什么,它可能是必要的宣布结果的类型。因此使用theDISASSEMBLE和分析器是你的朋友。

  • basic =编译器需要类型声明,优化,但也可以推断某些类型。标准语言的类型是已知的。

更好的编译器抱怨类型错误,可以跨函数传播类型,并且可以在某些优化不可能的时候发生抱怨。

顺序功能

注意序列功能是一种特殊的情况下艰难。 序列具有作为亚型列表载体(包括字符串)。

比方说,一个序列的功能是:

(foo result-type sequence-1 sequence-2 fn) 
  • 如果序列是同类型的,人们可能会想有一个优化的代码版本列表,另一个用于载体。

  • 如果序列是不同类型的,则将一个序列转换为不同类型可能是有用的。也许不会。

  • 结果类型也具有影响,这取决于结果类型,不同的算法是可能的/必要

所以自由度是相当高的。编译器可能有助于快速编写代码。但是特定序列函数的实现也许能够在运行时进行一些优化。

然后fn是一个接受元素并产生新元素的函数。知道它的类型签名可能会有帮助 - 不然。

我真的不知道哪个当前的Common Lisp具有复杂的序列函数实现。虽然我记得Symbolics Common Lisp的实现给了它一些努力。

文档和文件

通常什么编译器可以优化以及如何没有很好的记载,如果在所有。有一些关于这个话题的论文,但是他们经常是老的和/或过时的。

+0

真的很棒的答案,非常感谢您的代码,以及解释和所有链接!头脑的食物。 – Renzo