[plt-scheme] From hash-table to generator

From: Jens Axel Søgaard (jensaxel at soegaard.net)
Date: Fri May 11 10:15:19 EDT 2007

>> The problem is thus to write hash-table->generator efficiently. As it 
>> turns out, the inversion via call/cc looses big time to the 
>> convert-to-list approach.
>>
>> So the next question, is: Is there a better way to turn a hash-table 
>> into a generator?
> 
> Hi Jens,
> 
> I'm interested: how does the following approach compare, 
> performance-wise, to the version with continuations?
> 
> ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
> (module hash-table-generator mzscheme
>   (require (lib "etc.ss")
>            (lib "contract.ss"))
> 
>   (provide/contract [hash-table->generator
>                      (hash-table? . -> . (-> any/c))])
>   (define (hash-table->generator ht)
>     (local
>         ((define ch (make-channel))
>          (define sentinel (cons 'done 'done))
>          (define all-done? #f))
>       (thread (lambda ()
>                 (hash-table-for-each
>                  ht
>                  (lambda (k v)
>                    (channel-put ch k)))
>                 (channel-put ch sentinel)))
>       (lambda ()
>         (cond
>           [all-done? #f]
>           [else
>            (let ([result (channel-get ch)])
>              (cond
>                [(eq? result sentinel)
>                 (set! all-done? #t)
>                 #f]
>                [else
>                 result]))])))))

We have the thread version above.

The call/cc version is:


(define (hash-table->generator ht)
   (let ([continue 'start]
         [return   #f])
     (lambda ()
       (let/cc ret
         (set! return ret)
         (case continue
           [(done)    (return #f)]
           [(start)   (hash-table-for-each
                       ht
                       (lambda (x v)
                         (call/cc
                          (lambda (k)
                            (set! continue k)
                            (return x)))))
                      (set! continue 'done)
                      (return #f)]
           [else      (continue)])))))

And a convert-to-list-first version:

(define (hash-table->generator ht)
   (let ([xs (hash-table->list ht)])
     (lambda ()
       (cond
         [(null? xs) #f]
         [else       (let ([x (car xs)])
                       (set! xs (cdr xs))
                       x)]))))

In my sample application I get:

   Above version:    cpu time: 23594 real time: 25469 gc time: 3389
   Call/cc version:  cpu time: 18125 real time: 18344 gc time: 11515
   List version:     cpu time: 1843 real time: 1843 gc time: 578

The conclusion must therefore be, that you need to
implement hash-table->generator as a primitive, if you want
to avoid Unnecessary allocation.

-- 
Jens Axel Søgaard



Posted on the users mailing list.