两种组合功能的方法,效率如何不同?

时间:2009-10-04 17:30:35

标签: algorithm performance scheme

让f将一个值转换为另一个值,然后我编写一个重复转换n次的函数。

我提出了两种不同的方式:

  • 一个是显而易见的方式 字面上应用函数n 次,所以重复(f,4)表示 x→ F(F(F(F(X))))
  • 另一种方式是受到启发 快速的供电方法,意思是 把问题分成两部分 问题是一半大的问题 只要n是偶数。所以重复(f,4) 表示 x→g(g(x)),其中 g(x)= F(F(X))

起初我认为第二种方法不会提高效率。在一天结束时,我们仍然需要申请f次,不是吗?在上面的例子中, g 仍会被翻译成 f o f 而不做任何进一步的简化,对吗?

但是,当我尝试使用这些方法时,后一种方法的速度更快。


;; computes the composite of two functions
(define (compose f g)
  (lambda (x) (f (g x))))

;; identify function
(define (id x) x)

;; repeats the application of a function, naive way
(define (repeat1 f n)
  (define (iter k acc)
    (if (= k 0)
        acc
        (iter (- k 1) (compose f acc))))
  (iter n id))

;; repeats the application of a function, divide n conquer way
(define (repeat2 f n)
  (define (iter f k acc)
    (cond ((= k 0) acc)
          ((even? k) (iter (compose f f) (/ k 2) acc))
          (else (iter f (- k 1) (compose f acc)))))
  (iter f n id))

;; increment function used for testing
(define (inc x) (+ x 1))

事实上,((repeat2 inc 1000000)0)((repeat1 inc 1000000)0)快得多。我的问题是第二种方法在哪方面比第一种方法更有效?重新使用相同的函数对象是否会保留存储并减少创建新对象所花费的时间?

毕竟,应用程序必须重复n次,或者换句话说, x→((x + 1)+1)不能自动缩减为 x→( x + 2),对吧?

我在DrScheme 4.2.1上运行。

非常感谢。

2 个答案:

答案 0 :(得分:3)

你说两个版本对inc进行相同数量的调用是正确的 - 但还有更多 开销比代码中的开销高。具体来说,第一个版本创建N个闭包,而 第二个只创建log(N)闭包 - 如果闭包创建是大多数工作 然后你会看到性能上的巨大差异。

您可以使用三件事情来详细了解这些内容:

  1. 使用DrScheme的time特殊表格来衡量速度。除了它的时间 开始执行一些计算,它还会告诉你在GC中花了多少时间。 您将看到第一个版本正在进行一些GC工作,而第二个版本没有。 (嗯,确实如此,但它很少,它可能不会显示。)

  2. 你的inc函数做得很少,你只测量循环开销。 例如,当我使用这个糟糕的版本时:

    (define (slow-inc x)
      (define (plus1 x)
        (/ (if (< (random 10) 5)
             (* (+ x 1) 2)
             (+ (* x 2) 2))
           2))
      (- (plus1 (plus1 (plus1 x))) 2))
    

    两种用途之间的差异从~11降至1.6。

  3. 最后,试试这个版本:

    (define (repeat3 f n)
      (lambda (x)
        (define (iter n x)
          (if (zero? n) x (iter (sub1 n) (f x))))
        (iter n x)))
    

    它不做任何组合,它大致有效 与第二版相同的速度。

答案 1 :(得分:1)

第一种方法基本上应用了n次函数,因此它是O(n)。但第二种方法实际上并没有应用n次函数。每次调用repeat2时,只要n为偶数,它就会将n除以2。因此,大多数情况下问题的大小减半而不是仅减少1.这给出了总体运行时间O(log(n))。

正如Martinho Fernandez建议的那样,关于exponentiation by squaring explains的维基百科文章非常清楚。