代码之家  ›  专栏  ›  技术社区  ›  Phil

组成函数的两种方法,效率有什么不同?

  •  2
  • Phil  · 技术社区  · 15 年前

    让f把一个值转换成另一个值,然后我编写一个函数,它重复转换n次。

    我想出了两种不同的方法:

    • 一个是显而易见的方式 字面上应用函数n 时代,所以 重复(F,4) 方法 X射线 f(f(f(x)))
    • 另一种方式是从 快速供电方法,也就是说 把问题分成两部分 一半大的问题 当n为偶数时。所以 重复(F,4) 方法 X_ G(G(X))。 哪里 g(x)= f(f(x))

    起初,我认为第二种方法不会提高效率那么多。一天结束的时候,我们还需要申请N次,不是吗?在上面的例子中, G 仍然会被翻译成 F O F(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) (重复1 inc 1000000)0) . 我的问题是,第二种方法在哪方面比第一种方法更有效?重新使用相同的函数对象是否保留了存储空间并减少了创建新对象所花费的时间?

    毕竟,应用程序必须重复n次,或者用另一种方式说, X((X+1)+1) 不能自动减少到 X(2) 对吧?

    我正在运行DRScheme 4.2.1。

    非常感谢。

    2 回复  |  直到 15 年前
        1
  •  3
  •   Eli Barzilay    15 年前

    两个版本对 inc --但还有更多 开销比代码中的开销大。具体来说,第一个版本创建n个闭包,而 第二个只创建日志(n)闭包——如果闭包的创建是大部分工作的话 然后你会发现在性能上有很大的不同。

    您可以使用三种方法来更详细地了解这一点:

    1. 使用DrScheme time 测量速度的特殊形式。除了时间 为了执行一些计算,它还将告诉您在GC中花费了多少时间。 您将看到第一个版本正在做一些GC工作,而第二个版本没有。 (嗯,是的,但它太小了,很可能不会出现。)

    2. 你的 股份有限公司 函数的作用如此之小,以至于您只测量循环开销。 例如,当我使用这个坏版本时:

      (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)))
      

      它不做任何合成,它大致工作在 与第二个版本的速度相同。

        2
  •  1
  •   Community CDub    7 年前

    第一种方法本质上应用函数n次,因此它是o(n)。但第二种方法并不是实际应用函数n次。每次调用repeat2,当n为偶数时,它将n拆分为2。因此,大部分时间问题的规模减少了一半,而不仅仅是减少了1。这给出了O(log(n))的总体运行时间。

    AS Martinho Fernandez 建议,维基百科关于 exponentiation by squaring explains 很清楚。