当有多个参数时,memoize会变慢

时间:2015-11-03 14:35:01

标签: racket

我从here找到了一个常规备忘录,这使得fib更快

(define (memoize fn)
  (let ((cache (make-hash)))
    (λ arg (hash-ref! cache arg (thunk (apply fn arg))))))

(define fib
  (memoize
   (lambda (n)
     (if (< n 2) n (+ (fib (sub1 n)) (fib (- n 2)))))))

我尝试在自然递归中实现Racket中的interleaving string问题

(define (is-interleave-rec s1 s2 s3)
  (if (eq? (+ (string-length s1) (string-length s2))
           (string-length s3))
      (aux-rec s1 0 s2 0 s3 0)
      #f))

(define (aux-rec s1 p1 s2 p2 s3 p3)
  (cond
    [(eq? p3 (string-length s3)) #t]
    [(eq? p1 (string-length s1))
     (equal? (substring s2 p2) (substring s3 p3))]
    [(eq? p2 (string-length s2))
     (equal? (substring s1 p1) (substring s3 p3))]
    [(and (eq? (string-ref s1 p1) (string-ref s3 p3))
          (eq? (string-ref s2 p2) (string-ref s3 p3)))
     (or (aux-rec s1 (add1 p1) s2 p2 s3 (add1 p3))
         (aux-rec s1 p1 s2 (add1 p2) s3 (add1 p3)))]
    [(eq? (string-ref s1 p1) (string-ref s3 p3))
     (aux-rec s1 (add1 p1) s2 p2 s3 (add1 p3))]
    [(eq? (string-ref s2 p2) (string-ref s3 p3))
     (aux-rec s1 p1 s2 (add1 p2) s3 (add1 p3))]
    [else #f]))

然后是一个memoization版本

(define (is-interleave-mem s1 s2 s3)
  (if (eq? (+ (string-length s1) (string-length s2))
           (string-length s3))
      (aux-mem s1 0 s2 0 s3 0)
      #f))

(define aux-mem
  (memoize
   (λ (s1 p1 s2 p2 s3 p3)
     (cond
       [(eq? p3 (string-length s3)) #t]
       [(eq? p1 (string-length s1))
        (equal? (substring s2 p2) (substring s3 p3))]
       [(eq? p2 (string-length s2))
        (equal? (substring s1 p1) (substring s3 p3))]
       [(and (eq? (string-ref s1 p1) (string-ref s3 p3))
             (eq? (string-ref s2 p2) (string-ref s3 p3)))
        (or (aux-mem s1 (add1 p1) s2 p2 s3 (add1 p3))
            (aux-mem s1 p1 s2 (add1 p2) s3 (add1 p3)))]
       [(eq? (string-ref s1 p1) (string-ref s3 p3))
        (aux-mem s1 (add1 p1) s2 p2 s3 (add1 p3))]
       [(eq? (string-ref s2 p2) (string-ref s3 p3))
        (aux-mem s1 p1 s2 (add1 p2) s3 (add1 p3))]
       [else #f]))))

令我惊讶的是,memoization版本较慢,测试用例是

(define as (make-string 10000 #\a))
(define zs (make-string 10000 #\z))
(define bs (make-string 10000 #\b))

(define az (string-append as zs))
(define abz (string-append as bs zs))

(time (is-interleave-rec az bs abz))
(time (is-interleave-mem az bs abz))

结果将是

cpu time: 4 real time: 4 gc time: 0
#t
cpu time: 5333 real time: 5348 gc time: 67
#t

我认为原因是哈希表有很多参数,我想知道是否可以改进它?

1 个答案:

答案 0 :(得分:3)

我更改了代码如下:

 
(define allcalls 0)
(define memcalls 0)

(define (memoize fn)
  (let ((cache (make-hash)))
    (λ arg
      (set! allcalls (add1 allcalls))
      (hash-ref! cache arg
                 (thunk
                  (set! memcalls (add1 memcalls))
                  (apply fn arg))))))

跟踪调用aux-mem的次数,以及调用基础过程的次数。

添加

(displayln allcalls)
(displayln memcalls)

最后我

cpu time: 2 real time: 2 gc time: 0
#t
cpu time: 7046 real time: 7040 gc time: 30
#t
20001
20001

意味着aux-mem永远不会使用相同的参数调用两次。

所以你的记忆完全无效(毕竟,记忆的目的是返回一个已经被要求并因此在之前计算过的结果),而这里所做的只是增加开销。