Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Separating allocation from code - NDC

chrismdp
December 05, 2014

Separating allocation from code - NDC

My development career has taken me from AAA games to high performance server programming to front end web applications. Along the way, I've learnt a lot through following seemingly counter-intuitive advice.

I was taught to write tests before my code. This made no sense at first, but gradually it became less about writing tests, and more about good software design. I was taught to separate allocation from code. This made no sense at first, but then I began to realise that John McCarthy was possibly right after all... and that Philip Greenspun had a point.

Join me for a tour through software development paradigms, games, dependency injection, data oriented programming, the NIH syndrome and the fundamental limitations of computing. Hopefully you'll emerge with a better idea of where high performance computing is going, and how to get ahead of the curve.

Warning: There might be C++ code from time to time, but it will be painless. Promise.

chrismdp

December 05, 2014
Tweet

More Decks by chrismdp

Other Decks in Programming

Transcript

  1. Separating Allocation from Code A journey of discovery (

  2. Separating Allocation from Code A blob implementation in C++ (

  3. Separating Allocation from Code Things I learnt coding AAA games

    that I’ve had to unlearn (
  4. Separating Allocation from Code How my mind was blown repeatedly

    (
  5. Separating Allocation from Code How John McCarthy was right after

    all (
  6. Separating Allocation from Code The endless quest for a descriptive

    subtitle (
  7. Elixir Studios (

  8. ()

  9. class Monster : public Entity { int size; string name;

    int attributes[100]; Animation* animation; public: Monster(string const& n, EntityTemplate* t) { size = t->size; name = n; animation = new Animation(n + “.ani”); } }; (
  10. vector<Monster*> monsters; Monster* m = new Monster(t, “Shrek”); monsters.push_back(m); (

  11. auto it = monsters.begin(); for (; it != monsters.end(); ++it)

    { if ((*it)->size > SMALL) { render(*it); } } ()
  12. Ruby on Rails, Coaching, Training ()

  13. Sol Trader soltrader.net ()

  14. Blob ()

  15. Blob ()

  16. ()

  17. “I want to say something about implementing blobs that also

    applies to programming in general. Separate your memory allocation policy from your algorithm code. A higher level module should be used to glue the two together in the most optimal way. It always troubles me to see modules that allocate their own memory…” www.johnmccutchan.com/2011/10/programmer-and-his-blobs.html ()(
  18. class Blob { int8* data; Blob(size_t size) { data =

    new int8[size]; } … }; Blob* obj = new Blob(1024); ()(
  19. struct Blob { int8* data; size_t size; }; int8 backend[1024];

    Blob blob = { backend, 1024 }; ()(
  20. struct Blob { int8* data; size_t size; }; int8* backend

    = new int8[1024]; Blob blob = { backend, 1024 }; ()(
  21. struct Blob { int8* data; size_t size; }; int8* file

    = getFileArray(“foo.txt”); Blob blob = { file, 1024 }; ()(
  22. “Tying allocation to code is a form of coupling, and

    coupling sucks.” (()(
  23. Tying allocation to code is a form of coupling bit.ly/1pHzTNf

    (()(
  24. (()() class Blob { char *_data; public: Blob() : _data(NULL)

    {} Blob(char* data) : _data(data) {} template <typename T> T* get(unsigned offset) const { return (T*)(_data + offset); } };
  25. Integer(Blob blob) : _blob(blob) {} void set(int value) { _blob.set1(0,

    BL_INT); _blob.set4(1, value); } (()()
  26. Array(Blob blob, unsigned capacity) { _blob = blob; _blob.set1(0, BL_ARRAY);

    _blob.set4(CAPACITY_OFFSET, capacity); _blob.set4(USED_OFFSET, FIRST_VALUE); _blob.set4(SIZE_OFFSET, 0); } char data[128]; Blob blob(data) Array array(blob, 128); TYPE CAPACITY USED (13) SIZE (0) 13 0 (()()
  27. void Array::push_back(int value) { fix(place<Integer>().set(value)); } TYPE CAPACITY USED (18)

    SIZE (1) TYPE VALUE 13 0 18 (()()
  28. Results Random access was slower Sequential iteration was faster (()()

  29. RANDOM ACCESS Random access was at least an order of

    magnitude slower for all sizes These blobs aren’t designed for random access ((()()
  30. SEQUENTIAL TRAVERSAL With a map containing 50 vectors of 500

    numbers each: STL implementation was ~20% quicker With a map containing 500 vectors of 50 numbers each: Blob implementation was ~30% quicker ((()()
  31. void Array::push_back(int value) { fix(place<Integer>().set(value)); } ((())()

  32. struct Blob { int8* data; size_t size; }; int8 backend[1024];

    Blob blob = { backend, 1024 }; ((())()
  33. ((())()()

  34. class AiData { char _goalData[GOAL_DATA_SIZE]; char _knData[MAX_ACTORS][ACTOR_K_SIZE]; char _qData[MAX_ACTORS][ACTOR_Q_SIZE]; char

    _sharedKnData[SHARED_K_SIZE]; char _sharedQData[SHARED_Q_SIZE]; }; AiData* ai = new AiData; ((())()()
  35. Data-oriented design ((())()()

  36. Memory is slow ((())()()

  37. Memory is getting slower (relatively) ((())()()

  38. Memory is expensive ((())()()

  39. CPU Intel Core i5 Haswell L2 cache (512 KB) CORE

    + L1 (64 KB) L3 cache (3,072 KB) Main Memory (16,777,216 KB) Bus CORE + L1 (64 KB) This presentation brought to you by… ((())()()
  40. class Blob { int8* data; Blob(size_t size) { data =

    new int8[size]; } … }; Blob* obj = new Blob(1024); ((())()()
  41. CPU Blob (Heap) Data (Heap) 1 2 ((())()()

  42. sed https://www.flickr.com/photos/k_putt/sets/72157644129911474/ ((())()()

  43. ((())()() auto it = monsters.begin(); for (; it != monsters.end();

    ++it) { if ((*it)->size > SMALL) { render(*it); } } Monsters render() render() render()
  44. ((())()() auto it = monsters.begin(); for (; it != monsters.end();

    ++it) { // update, then if ((*it)->sizeChanged()) { copyToRender(*it, monstersToRender); } } Monsters
  45. Monsters MonstersToRender ((())()()

  46. MonstersToRender render() render() render() ((())(s)() auto it = monstersToRender.begin(); for

    (; it != monstersToRender.end(); ++it) { render(*it); }
  47. Advantages ((())(s)()

  48. Parallelisation is cheap MonstersToRender Thread 1 Thread 2 Thread 3

    ((())(s)p)
  49. ((())(s)p)

  50. (l(())(s)p) * * * * * * * * Monsters

    * * * MonstersToRender vector<Monster*> monsters;
  51. (l(())(s)p) M M M M M M M M Monsters

    M M M MonstersToRender vector<Monster> monsters; Now we’re copying the object not a pointer!
  52. Unit testing is easy Monsters MonstersToRender (l(())(s)p)

  53. (l((i)(s)p) It’s rather like… Functional Programming

  54. Functional programming We mapping data to data - easy to

    reason about, referential transparency for free You’re copying data (or modifying in place as an optimisation) - levering the properties of immutability.
  55. So what?

  56. Optimise early! (sometimes) Sometimes you need to to completely change

    your paradigm.
  57. Gold-plate it! (sometimes) Sometimes you need to keep cleaning code,

    learning and investigating. It’s amazing what you’ll find out. Kent Beck: “I’m in the habit of trying stupid things out to see what happens”
  58. Pay attention

  59. Web developers You’re already doing this kind of work with

    SQL Consider your data before building elaborate and messy OO code Thinking this way helps to break work into asynchronous jobs Keep an eye on Gary Bernhardt’s new Structured Design work
  60. C++/C# developers Check out data-oriented design if you haven’t already

    Be wary of naïve OO design The future of performance is parallelisation Don’t write a program that’s hard to parallelise Unlock the power of modern GPUs
  61. Questions? [email protected] / @chrismdp on Twitter I coach and team

    teams on agile, code quality, and BDD I make Sol Trader http://soltrader.net and Card Pirates http://cardpirates.com I co-founded Kickstart Academy
 http://kickstartacademy.io