2

我正在使用一个太大而无法放入内存的数据集,因此想使用流来处理数据。但是,我发现流并没有像我预期的那样限制使用的内存量。示例代码说明了该问题,如果内存限制设置为 128mb,则会耗尽内存。我知道我可以提高内存限制,但是对于我想要使用的那种大数据集,这不是一个选择。我应该如何减少内存使用?

#lang racket

(struct prospects (src pos num max-num)
  #:methods gen:stream
  [(define (stream-empty? stream)
     (equal? (prospects-num stream) (prospects-max-num stream)))
   ;
   (define (stream-first stream)
     (list-ref (prospects-src stream) (prospects-pos stream)))
   ;
   (define (stream-rest stream)
     (let ([next-pos (add1 (prospects-pos stream))]
           [src (prospects-src stream)]
           [next-num (add1 (prospects-num stream))]
           [max-num (prospects-max-num stream)])
       (if (< next-pos (length src))
           (prospects src next-pos next-num max-num)
           (prospects src 0 next-num max-num))))])

(define (make-prospects src num)
  (prospects src 0 0 num))

(define (calc-stats prospects)
  (let ([just-a (stream-filter
                 (λ (p) (equal? "a" (vector-ref p 0)))
                 prospects)]
        [just-b (stream-filter
                 (λ (p) (equal? "b" (vector-ref p 0)))
                 prospects)])
    ;
    (let ([num-a (stream-length just-a)]
          [num-b (stream-length just-b)]
          [sum-ref1-a (for/sum ([p (in-stream just-a)])
                        (vector-ref p 1))]
          [sum-ref1-b (for/sum ([p (in-stream just-b)])
                        (vector-ref p 1))])
      ;
      #|
          ; Have also tried with stream-fold instead of for/sum as below:
          [sum-ref1-a (stream-fold
                       (λ (acc p) (+ acc (vector-ref p 1)))
                       0 just-a)]
          [sum-ref1-b (stream-fold
                       (λ (acc p) (+ acc (vector-ref p 1)))
                       0 just-b)])
      |#
      ;      
      (list num-a num-b sum-ref1-a sum-ref1-b))))

;================================
;           Main
;================================
(define num-prospects 800000)

(define raw-prospects (list #("a" 2 2 5 4 5 6 2 4 2 45 6 2 4 5 6 3 4 5 2)
                            #("b" 1 3 5 2 4 3 2 4 5 34 3 4 5 3 2 4 5 6 3)))

(calc-stats (make-prospects raw-prospects num-prospects))

注意:创建此程序只是为了演示问题;真正的流将访问数据库以引入数据。

4

3 回答 3

3

您的代码中的主要问题是您试图通过流进行多次传递。(每次调用stream-length都是一次传递,而对for/sum(或stream-fold,就此而言)的每次调用都是另一次传递。)这意味着您必须实现整个流,而不允许对较早的流元素进行垃圾收集。

这是对您的代码的修改,仅通过一次。请注意,我num-prospects在我的版本中设置为 8,000,000,因为即使是多通道版本也没有在我的系统上用完只有 800,000 的内存:

#lang racket
(require srfi/41)

(define (make-prospects src num)
  (stream-take num (apply stream-constant src)))

(define (calc-stats prospects)
  (define default (const '(0 . 0)))
  (define ht (for/fold ([ht #hash()])
                       ([p (in-stream prospects)])
               (hash-update ht (vector-ref p 0)
                            (λ (v)
                              (cons (add1 (car v))
                                    (+ (cdr v) (vector-ref p 1))))
                            default)))
  (define stats-a (hash-ref ht "a" default))
  (define stats-b (hash-ref ht "b" default))
  (list (car stats-a) (car stats-b) (cdr stats-a) (cdr stats-b)))

;================================
;           Main
;================================
(define num-prospects 8000000)

(define raw-prospects '(#("a" 2 2 5 4 5 6 2 4 2 45 6 2 4 5 6 3 4 5 2)
                        #("b" 1 3 5 2 4 3 2 4 5 34 3 4 5 3 2 4 5 6 3)))

(calc-stats (make-prospects raw-prospects num-prospects))

我应该澄清一下,使用的srfi/41只是为了编写一个更有效的版本make-prospects(虽然,参考实现stream-constant不是很有效,但仍然比你的prospects流生成器​​更有效);calc-stats不使用它。

于 2013-09-05T11:26:00.807 回答
3

我赞成克里斯的出色回答,并建议您选择它以标记为已接受。

但是,对于不适合 RAM 的数据集,什么会使用最少的内存?可能类似于以下伪代码:

(require db)
(define dbc <open a db connection>)
(define just-a (query-value dbc "SELECT Count(*) FROM t WHERE n = $1" "a"))
(define just-b (query-value dbc "SELECT Count(*) FROM t WHERE n = $1" "b"))

为什么这是一个有点聪明的答案:

  • 表面上你问过关于使用 Racket 流来处理不适合内存的事情。

  • 如果您需要比计数(或 sum/min/max)更复杂的聚合,则需要编写更复杂的 SQL 查询,并且可能希望在服务器上将它们存储过程。

为什么它不一定是 smartypants:

  • 您确实提到了您的真实用例涉及数据库。;)

  • 数据库服务器和 SQL 的一个特点是无法放入内存的大型数据集。利用服务器通常会击败以通用语言重新实现的 DB-ish(以及可能对同一 DB 服务器的其他用途/用户更友好)。

于 2013-09-05T13:34:21.190 回答
1

您的内存不足是因为您在遍历流时挂在了流的头部。GC 无法收集任何内容,因为由于您有指向头部的指针,因此流的每个元素仍然可以访问。

为了演示,使用此流:

(define strm (make-prospects raw-prospects num-prospects))

这爆炸了:

(define just-a (stream-filter (λ (p) (equal? "a" (vector-ref p 0))) strm))
(stream-length just-a)

虽然这很好:

(stream-length (stream-filter (λ (p) (equal? "a" (vector-ref p 0))) strm))
于 2013-09-09T06:24:22.290 回答