lips icon indicating copy to clipboard operation
lips copied to clipboard

Investigate and document performance

Open jcubic opened this issue 5 years ago • 5 comments

Here is example of slow and fast code for calculating factorial of 1000:

;; slow
(--> (new Array 1000) (fill 0) (map (curry (n-ary 3 +) 1)) (reduce (binary *)))

;; same but faster
(--> (new Array 1000) (fill 0) (map (lambda (_ i) (+ i 1))) (reduce (lambda (a b) (* a b))))

;; faster and simpler
(apply * (cdr (range 1001)))

jcubic avatar Nov 03 '20 14:11 jcubic

Speed test, array based code is much faster even that that is lambda that invoke the interpreter in each iteration:

(define (get-zeros data)
  (--> data
       (split "\n")
       (filter (lambda (line)
                  (line.match #/ZERO;Nd;/
                              )))
       (map (lambda (line)
               (let ((parts (line.split ";")))
                 (number->string (string->number (. parts 0) 16)))))))


(define (get-zeros data)
  (let* ((lines (data.split "\n"))
         (re #/ZERO;Nd;/
            )
         (len (vector-length lines))
         (result (vector)))
    (do ((i 0 (+ i 1)))
      ((>= i len) result)
      (let ((line (. lines i)))
        (if (not (null? (line.match re)))
            (let* ((parts (line.split ";"))
                   (number (number->string (string->number (. parts 0) 16))))
              (result.push number)))))))

jcubic avatar Jan 02 '21 10:01 jcubic

The different is huge Simple array methods:

real	0m2,536s
user	0m3,113s
sys	0m0,094s

do macro:

real	0m44,835s
user	0m45,418s
sys	0m0,133s

Probably each lambda slow down the execution, so less code is always better. Also using just JavaScript code in Scheme would probably be the fastest.

Example modifying the function to use JS functions:

(define (get-zeros data)
  (--> data
       (split "\n")
       (filter (lambda (line)
                  (line.match #/ZERO;Nd;/
                              )))
       (map (lambda (line)
               (let ((parts (line.split ";")))
                 (--> (parseInt (. parts 0) 16) (toString)))))))

There is small improvements but there only

real	0m2,503s
user	0m3,063s
sys	0m0,103s

Defining regex outside of function:

real	0m2,475s
user	0m3,037s
sys	0m0,099s

Those are not reliable, benchmark need to be run multiple times and use average to be representative.

jcubic avatar Jan 02 '21 11:01 jcubic

This is probably the fastest function:

(define (get-zeros data)
  (let ((re #/ZERO;Nd;/
            ))
    (--> data
         (split "\n")
         (filter (lambda (line)
                   (line.match re)))
         (map (lambda (line)
                (let* ((parts (line.split ";"))
                       (number (parseInt (. parts 0) 16)))
                  (number.toString)))))))

Only native functions and no macros.

real	0m2,478s
user	0m3,040s
sys	0m0,078s

When there will be macro expansion time it will allow to use Scheme based macros.

jcubic avatar Jan 02 '21 11:01 jcubic

I should probably use some benchmarking tool to test performance: This SO question have example of using Benchmark.js. How to profile Javascript now that JSPerf is down?

But the usage of this tools is simple https://benchmarkjs.com/

jcubic avatar Jan 02 '21 11:01 jcubic

Literals regexes are slightly faster, probably because they are created in parser and getting value from scope chain is slower.

>>> Regex: variable x 28.59 ops/sec ±2.58% (51 runs sampled)
>>> Regex: literal x 29.99 ops/sec ±2.52% (53 runs sampled)
Fastest is Regex: literal

jcubic avatar Jan 02 '21 13:01 jcubic