Things in that last version weren't robust/efficient with regards to zero 
length strings being in the mix. This fixes that: 
    
    
    proc lcpLenVRange(strs: openArray[string]): int =
      if strs.len == 0: return -1     # raise?
      if strs[0].len == 0: return 0   # ANY "" anywhere => 0
      let byte0 = strs[0][0]
      var minAt, maxAt: int
      for i in 1 ..< strs.len:
        if strs[i].len == 0: return 0
        if strs[i] < strs[minAt]: minAt = i
        if strs[i] > strs[maxAt]: maxAt = i
        if strs[i][0] != byte0: return 0
      for i, c in strs[minAt]:
        if c != strs[maxAt][i]: return i
      return strs[minAt].len
    
    
    Run

In the interests of being mostly self-contained, I also pushed a better 
`timeIt` to `cligen#head` and updated my driver to use the new `timeIt` and to 
**not** time the `stdout.write` parts: 
    
    
    proc timeAlgo(algo=lcpVRange, reps=1, strs: seq[string]) =
      var x: int
      timeIt(stdout.write, "", 1e-6, 3, " usec via " & ($algo)[3..^1], reps):
        x += strs.lcpLen(algo)
      echo " ", int(x.float / reps.float + 0.5)
    
    
    Run

Finally, I created a `benchLcp.zsh` script: 
    
    
    #!/bin/zsh
    lcp=`pwd`/lcp                   #We need Zsh only for its **
    echo "L3 data, 19 answer"
    for a in lcpVe lcpR lcpB lcpVR; do $lcp -r10 -a$a /usr/lib/python3.6/**; 
done
    echo "L2 data, 9 answer"
    for a in lcpVe lcpR lcpB lcpVR; do $lcp -r10 -a$a /usr/bin/*; done
    echo "L3 data, 0 answer"
    for a in lcpVe lcpR lcpB lcpVR; do (cd /usr/lib/python3.6; $lcp -r10 -a$a 
**); done
    echo "L2 data, 0 answer"
    for a in lcpVe lcpR lcpB lcpVR; do (cd /usr/bin; $lcp -r10 -a$a *); done
    
    
    Run

and used a little `nim-pgo` wrapper I have to use gcc's profile guided 
optimization (`-fprofile-generate` and `-fprofile-use`), compiling, running a 
test/bench, then re-compiling. With all that, I get: 
    
    
    L3 data, 19 answer
     1910.448..2253.771 1959.491 +- 32.812 usec via Vertical 19
     686.884..1150.131 777.125 +- 48.653 usec via Range 19
     922.680..1522.064 996.113 +- 58.569 usec via BinSearch 19
     693.560..1163.244 786.591 +- 49.932 usec via VRange 19
    L2 data, 9 answer
     41.246..63.181 45.848 +- 2.480 usec via Vertical 9
     37.193..54.359 40.936 +- 1.778 usec via Range 9
     22.650..25.511 23.413 +- 0.353 usec via BinSearch 9
     36.716..55.075 41.509 +- 2.292 usec via VRange 9
    L3 data, 0 answer
     0.000..0.954 0.167 +- 0.094 usec via Vertical 0
     535.488..1017.570 627.780 +- 48.740 usec via Range 0
     58.651..205.994 82.874 +- 15.300 usec via BinSearch 0
     0.715..3.576 1.097 +- 0.278 usec via VRange 0
    L2 data, 0 answer
     0.000..0.000 0.000 +- 0.000 usec via Vertical 0
     32.663..38.862 33.927 +- 0.650 usec via Range 0
     3.099..4.768 3.290 +- 0.166 usec via BinSearch 0
     0.000..0.238 0.048 +- 0.032 usec via VRange 0
    
    
    Run

which makes total sense to me. The @marks performance discrepancies (except his 
mysterious `foldl` bit), probably just come from the "mix"/averaging of how 
many zero length answers are in his inputs.

So, this new variant of that VRange hybrid method would make the most sense to 
recommend for general usage (of the above selection, anyway). It's never that 
slow, fastest at large scale when performance matters most, and can leverage 
early exit in easy cases.

The binary search way for smaller scale data may be best if the caller already 
knows and can pass in a data scale parameter. If we must measure the data size, 
that takes a whole pass through the length fields. That measurement eats into 
the 36/22=1.6x L2-non-zero speed ratio, probably reducing it to like 1.3x which 
isn't that much a boost for all the complexity of measurement+algorithm 
switching. I got about a 1.15x boost from PGO and most probably skip that 
entirely to avoid build complexity.

Reply via email to