Fast array access using Racket FFI

Pet*_*rin 6 opencv ffi racket

I am trying to write OpenCV FFI in Racket and arrived at a point where arrays need to be manipulated efficiently. However, all my attempts to access arrays by using Racket FFI resulted in very inefficient code. Is there a way for fast access of C arrays using FFI?

In Racket, this type of manipulation is reasonably fast, i.e.:

(define a-vector (make-vector (* 640 480 3)))
(time (let loop ([i (- (* 640 480 3) 1)])
    (when (>= i 0)
      ;; invert each pixel channel-wise
      (vector-set! a-vector i (- 255 (vector-ref a-vector i)))
      (loop (- i 1)))))
->  cpu time: 14 real time: 14 gc time: 0
Run Code Online (Sandbox Code Playgroud)

Now, in OpenCV, there is a struct called IplImage that looks like this:

typedef struct _IplImage
{
    int  imageSize;             /* sizeof(IplImage) */
    ...
    char *imageData;        /* Pointer to aligned image data.*/
}IplImage;
Run Code Online (Sandbox Code Playgroud)

The struct is defined in Racket as follows:

(define-cstruct _IplImage
    ([imageSize _int]
     ...
     [imageData _pointer]))
Run Code Online (Sandbox Code Playgroud)

Now we load an image using cvLoadImage function as follows:

(define img
  (ptr-ref
   (cvLoadImage "images/test-image.png" CV_LOAD_IMAGE_COLOR)
   _IplImage))
Run Code Online (Sandbox Code Playgroud)

The pointer imageData can be accessed by: (define data (IplImage-imageData img)))

Now, we want to manipulate data, and the most efficient way I could come up with was by using pointers:

(time (let loop ([i (- (* width height channels) 1)]) ;; same 640 480 3
    (when (>= i 0)
      ;; invert each pixel channel-wise
      (ptr-set! data _ubyte i (- 255 (ptr-ref data _ubyte i)))
      (loop (- i 1)))))
-> cpu time: 114 real time: 113 gc time: 0
Run Code Online (Sandbox Code Playgroud)

This is very slow, compared to the speed of native Racket vectors. I also tried other ways, such as _array, _cvector that don't even come close to the speed of using pointers, except for writing a first-class function in C that gets a function for running over the whole array. This C function is compiled to a library and bound in Racket using FFI. Then, Racket procedures can be passed to it and applied to all elements of the array. The speed was the same as with pointers, but still not sufficient to continue porting OpenCV library to Racket.

Is there a better way to do this?

Pet*_*rin 7

我尝试了 Eli 建议的方法,结果成功了!这个想法是使用字节串。由于在这种情况下数组的大小是已知的,(make-sized-byte-string cptr length)因此可以使用:

(define data (make-sized-byte-string (IplImage-imageData img)
                                     (* width height channels)))
Run Code Online (Sandbox Code Playgroud)

这导致运行时间接近 Racket 的原生向量:

(time (let loop ([i (- (* 640 480 3) 1)])
    (when (>= i 0)
      ;; invert each pixel channel-wise
      (bytes-set! data i (- 255 (bytes-ref data i)))
      (loop (- i 1)))))
-> cpu time: 18 real time: 18 gc time: 0
Run Code Online (Sandbox Code Playgroud)

谢谢你,伊莱。