Fast by default: everyday algorithmic thinking for developers

Fast by default: everyday algorithmic thinking for developers

We've grown to rely on high level frameworks so much that we no longer know how computers work. So when we're stuck with bad performance, simply changing frameworks won't help — you have to understand what's going on under the hood, and what to do with it. That's what algorithmic thinking is about.

Does the app have a bottleneck, and how do you identify it? Does this code really have to be slow, or is it doing unnecessary work? How do we achieve the same result while doing less? Can we delay a part of the work? Can we order the data differently for faster processing? When you practice algorithmic thinking, answering those questions becomes second nature, and eventually you learn to write code that's fast from the start, by default. So let me introduce you to algorithms again, from scratch, in a way that is useful for your everyday work.

6d07e6d95a43357254698ce9723350e6?s=128

Vladimir Agafonkin

September 12, 2018
Tweet

Transcript

  1. ⚡fast by default: everyday algorithmic thinking for developers Vladimir Agafonkin

  2. obsessed with performance

  3. ❤❤❤ open source

  4. 1. Find a narrowly scoped task 2. ⚡ Make the

    world’s fastest and simplest JavaScript library for it 3. Back to step 1
  5. Leaflet, mapbox-gl-js, mapbox-gl-native, earcut, earcut.hpp, rbush, rbush-knn, kdbush, kdbush.hpp, geokdbush,

    flatbush, geoflatbush, concaveman, supercluster, supercluster.hpp, dobbyscan, delaunator, d3-delaunay, linematch, lineclip, pixelmatch, simplify-js, cheap-ruler, polylabel, tinyqueue, flatqueue, tile-cover, which-polygon, quickselect, simple-statistics, tiny-sdf, geojson-vt, potpack, geojson-vt-cpp, geobuf, pbf, tile-reduce, geojson.hpp, geometry.hpp, tile-decorator, mbtiles- extracts, webgl-wind, suncalc, flamebearer, simpleheat, binary-split, magic-string, polysnap, rollup, sourcemap-codec
  6. github.com/mapbox/delaunator

  7. github.com/d3/d3-delaunay

  8. 1.delaunay: 150s 2.delaunay-fast: 117s 3.faster-delaunay: 5s 4.delaunator: 1.3s⚡ triangulating 1

    million points in JavaScript
  9. mapbox/earcut (JS) mapbox/earcut.hpp (C++)

  10. spatial indices

  11. github.com/mourner/rbush github.com/mourner/kdbush github.com/mourner/flatbush github.com/mourner/rbush-knn github.com/mourner/geokdbush github.com/mourner/geoflatbush

  12. ~ mapbox/geojson-vt (JS) mapbox/geojson-vt-cpp (C++)

  13. mapbox/supercluster (JS) mapbox/supercluter-hpp (C++)

  14. mapbox/supercluster (JS) mapbox/supercluter-hpp (C++)

  15. github.com/mapbox/webgl-wind

  16. github.com/mapbox/potpack

  17. None
  18. mathematical background? %

  19. None
  20. why understand algorithms?

  21. None
  22. 99% of performance bottlenecks are of algorithmic nature

  23. slow code: code which does unnecessary work

  24. None
  25. productive laziness: the art of avoiding unnecessary work

  26. algorithmic thinking: the art of avoiding unnecessary work by a

    machine
  27. your app frameworks

  28. None
  29. rollup/rollup#2062 rollup: map = magicString.generateMap() rollup: obj = decode(map)

  30. rollup/rollup#2062 rollup: map = magicString.generateMap() magic-string: ... map = encode(obj)

    rollup: obj = decode(map)
  31. rollup/rollup#2062 rollup: map = magicString.generateDecodedMap() magic-string: map = encode(obj) rollup:

    obj = decode(map) 40% faster source maps
  32. 1. identify a bottleneck 2. find out why it’s a

    bottleneck 3. optimize the bottleneck performance optimization: requires understanding algorithms
  33. None
  34. algorithmic complexity: and where to look for unnecessary work a

    way to describe how performance scales with input size
  35. O(1) O(log n) O(n) O(n log n) O(n^2) O(n^3)

  36. array[0] + array[1]; O(1) — instant

  37. O(n) — suspicious for (const item of array) { sum

    += item; }
  38. O(n2) — terribly slow for (let i = 0, n

    = array.length; i < n; i++) { for (let j = i + 1; j < n; j++) { sum += array[i] + array[j]; } }
  39. function foo(array) { for (const item of array) { array[array.indexOf(item)]

    = item + 10; } } O(n2) — terribly slow
  40. function foo(array) { for (const item of array) { bar(array,

    item); } } function bar(array, item) { array[array.indexOf(item)] = item + 10; } O(n2) — terribly slow
  41. accidentallyquadratic.tumblr.com

  42. O(n3) — unbearable, throw away for (let i = 0,

    n = array.length; i < n; i++) { for (let j = i + 1; j < n; j++) { for (let k = j + 1; k < n; k++) { sum += array[i] + array[j]; } } }
  43. 1. O(1) — instant 2. O(n) — suspicious 3. O(n2)

    — terribly slow 4. O(n3) — throw away
  44. 1. O(1) — 1 2. O(n) — 1000 3. O(n2)

    — 1,000,000 4. O(n3) — 1,000,000,000 (n = 1000)
  45. чорти б мене побрали!!! хай йому грець!! дідько! if (j

    < 0) { if (triangles.length === 0) triangles.push([i]); return; } for (let n = triangles.length, a = 0; a < n; ++a) { let sa = triangles[a]; if (sa[0] === j) { for (let b = a + 1; b < n; ++b) { let sb = triangles[b]; if (sb[sb.length - 1] === i) { triangles.splice(b, 1); triangles[a] = sa = sb.concat(sa); return; } } sa.unshift(i); return; } d3/d3-delaunay#23
  46. array.indexOf array.lastIndexOf array.splice array.includes array.reverse array.every Object.values array.find array.some array.findIndex

    array.reduce array.reduceRight array.some Object.keys O(n) — suspicious
  47. None
  48. array.slice array.concat array.map array.filter str.split suspicious allocations

  49. a.slice().concat(b).map(foo) .filter(bar).reduce(bla, 0); allocate memory and throw it away immediately

    (4 times)
  50. for (const b of items) { a.slice().concat(b).map(foo) .filter(bar).reduce(bla, 0); }

    allocate memory and throw it away immediately (4 times)
  51. functional programming! immutability! reactivity! damn allocations

  52. foo.forEach(a => { bar.forEach(b => { baz.forEach(c => { ...

    }); }); });
  53. JIT-optimizing JS engines don’t always understand what you want

  54. write predictable code so that the engine doesn’t have to

    guess
  55. Rich-Harris/sourcemap-codec#71 const lines = input.split(';'); for (const line of lines)

    { const segments = line.split(','); for (const segment of segments) { const decoded = decode(segment); for (const i of decoded) { ... } } }
  56. Rich-Harris/sourcemap-codec#71 for (let i = 0; i < input.length; i++)

    { const c = input.charCodeAt(i); if (c === 44) { // "," ... } else if (c === 59) { // ";" ... 2.7x faster decoding
  57. None
  58. O(1) O(n) O(log n) O(log n) — blazing fast

  59. 1 1,000,000 20 O(log n) — blazing fast

  60. binary search o(log n)

  61. None
  62. None
  63. None
  64. None
  65. None
  66. None
  67. rollup/rollup#2228 … 40x faster

  68. 1. O(n) → O(log n) 2. O(n log n) →

    O(n) 3. O(n2) → O(n log n) 4. O(n3) — throw away algorithmic optimization:
  69. slow slow slow algorithmic optimization:

  70. data processing algorithmic optimization:

  71. do more at the beginning to do less every next

    time algorithmic optimization:
  72. data structures: ways to organize data to search and update

    it efficiently
  73. • hash map • hash set • binary search tree

    • priority heap • linked list data structures: • interval tree • grid • r-tree • quadtree • kd-tree
  74. sorting an array: o(n log n)

  75. sorting an array so that smallest k items come first:

    o(n) github.com/mourner/quickselect
  76. github.com/mourner/kdbush

  77. priority tree: push: o(1) top: o(1) pop: o(log n) github.com/mourner/tinyqueue

    github.com/mourner/flatqueue
  78. • how often do you insert items? • how often

    do you remove items? • how often do you access items, and how? data structures:
  79. { '1': 10, '2': 20, '3': 30 }; { '0':

    10, '1': 20, ‘2': 30 }; HashTable Array [10, 20, 30];
  80. istanbuljs/istanbul-lib-instrument#22 { '1': 10, '2': 20, '3': 30 }; {

    '0': 10, '1': 20, ‘2': 30 }; 15x faster
  81. how I read academic papers and reference code

  82. 1. learn how things work under the hood 2. don’t

    hesitate to dig inside frameworks 3. contribute to open source 4. don’t be afraid to reinvent the wheel 5. simplify your code constantly 6. practice optimization, and you’ll learn how to write fast code from the start
  83. thank you