fix balancing errors
This commit is contained in:
4
seph.sml
4
seph.sml
@@ -1,4 +1,4 @@
|
||||
val data = #[(0, 0, "'Automerge is too slow and clunky. Martin (its principle architect and programmer) recorded himself typing an academic paper. Running his editing history through automerge (his own code) takes 490 seconds, which is a bit less than 10 minutes. Once processed, the editing trace sits on 1.1 GB of RAM. The newly merged performance branch (designed to fix a lot of these problems) is even slower - taking 750 seconds (12.5 minutes) to process the same editing trace.nI managed to get that 10 minute time down to 70ms (0.07 seconds). Thats the best result I've ever gotten from optimization work, and I'm delighted by it. Let me tell you what I did!nWhat does automerge do?nBefore we can go into detail about how I made automerge fast, we have to spend a moment talking about how automerge itself works.nAn automerge document is actually a tree of inserted characters. Each character in the document has the following properties:nA unique ID, made up of a tuple of (client ID, sequence number)nThe ID (or a pointer to) its parent item, which is the item directly before that character when it was inserted.nThe character itself ('A')nThere's a couple more fields (eg to mark when characters have been deleted), but essentially thats it. When a character is inserted in the document, automerge figures out the ID of the character immediately before the new character, and inserts the new character as one of its predecessor's *children*. If you just type a linear sequence of characters (as I'm doing right now), you'll end up with a big long chain of characters going down like a linked list.nSo why is automerge so slow?nWhen optimizing, I imagine myself manually doing all the work the computer is doing, one step at a time. Then I imagine asking: 'When I get bored, how would I speed this job up?'.nAutomerge is slow for 3 main reasons:nIts written in javascript and uses complex data structures. Javascript is reasonably fast for math, but slow and inefficient when using complex data structures.nAutomerge uses a complex and inefficient data structurenAutomerge makes extremely heavy use of immutablejsnEach of these issues accounts for about an order of magnitude slowdown in performance. You can see all 3 issues showing up in this method from the automerge source tree, which is called on each keystroke. Automerge uses this method to figure out where each new character should be placed in the resulting document:nfunction insertionsAfter(opSet, objectId, parentId, childId) {n let childKey = nulln if (childId) childKey = Map({opId: childId})nn return opSetn .getIn(['byObject', objectId, '_following', parentId], List())n .filter(op => op.get('insert') && (!childKey || lamportCompare(op, childKey) < 0))n .sort(lamportCompare)n .reverse() // descending ordern .map(op => op.get('opId'))n}nWhats wrong with this method?nThis method allocates all over the place. I can spot 5 allocations, not counting any extra nonsense immutablejs is doing. The call to List() has no effect as far as I can tell from reading immutablejs's documentation.nThe document is always kept in a sorted order anyway, so the calls to sort() and reverse() are unnecessary. The algorithm only needs to figure out where the new child should be inserted. Re-sorting all children is entirely avoidable. Sort functions are often fast when the input is sorted already, but in this case because the sorting function is inverted, the computer always has to sort the entire list.nYou can't tell from looking at this method, but insertionsAfter nDespite CRDTs being the 'new hotness' in the collaborative editing game for years, I've been resisting them. As I said in my [recent blog post about CRDTs](https://josephg.com/blog/crdts-are-the-future/), they've been generally unworkable for real world collaborative editing because:nThey take up too much space on disk and in memory. (Automerge takes 1.1GB in RAM to store a 100kb document)nThey consume way too much CPU to process editsnUntil these issues are addressed, I can't recommend CRDTs for use in general computing.n'"),
|
||||
val seph_arr = #[(0, 0, "'Automerge is too slow and clunky. Martin (its principle architect and programmer) recorded himself typing an academic paper. Running his editing history through automerge (his own code) takes 490 seconds, which is a bit less than 10 minutes. Once processed, the editing trace sits on 1.1 GB of RAM. The newly merged performance branch (designed to fix a lot of these problems) is even slower - taking 750 seconds (12.5 minutes) to process the same editing trace.nI managed to get that 10 minute time down to 70ms (0.07 seconds). Thats the best result I've ever gotten from optimization work, and I'm delighted by it. Let me tell you what I did!nWhat does automerge do?nBefore we can go into detail about how I made automerge fast, we have to spend a moment talking about how automerge itself works.nAn automerge document is actually a tree of inserted characters. Each character in the document has the following properties:nA unique ID, made up of a tuple of (client ID, sequence number)nThe ID (or a pointer to) its parent item, which is the item directly before that character when it was inserted.nThe character itself ('A')nThere's a couple more fields (eg to mark when characters have been deleted), but essentially thats it. When a character is inserted in the document, automerge figures out the ID of the character immediately before the new character, and inserts the new character as one of its predecessor's *children*. If you just type a linear sequence of characters (as I'm doing right now), you'll end up with a big long chain of characters going down like a linked list.nSo why is automerge so slow?nWhen optimizing, I imagine myself manually doing all the work the computer is doing, one step at a time. Then I imagine asking: 'When I get bored, how would I speed this job up?'.nAutomerge is slow for 3 main reasons:nIts written in javascript and uses complex data structures. Javascript is reasonably fast for math, but slow and inefficient when using complex data structures.nAutomerge uses a complex and inefficient data structurenAutomerge makes extremely heavy use of immutablejsnEach of these issues accounts for about an order of magnitude slowdown in performance. You can see all 3 issues showing up in this method from the automerge source tree, which is called on each keystroke. Automerge uses this method to figure out where each new character should be placed in the resulting document:nfunction insertionsAfter(opSet, objectId, parentId, childId) {n let childKey = nulln if (childId) childKey = Map({opId: childId})nn return opSetn .getIn(['byObject', objectId, '_following', parentId], List())n .filter(op => op.get('insert') && (!childKey || lamportCompare(op, childKey) < 0))n .sort(lamportCompare)n .reverse() // descending ordern .map(op => op.get('opId'))n}nWhats wrong with this method?nThis method allocates all over the place. I can spot 5 allocations, not counting any extra nonsense immutablejs is doing. The call to List() has no effect as far as I can tell from reading immutablejs's documentation.nThe document is always kept in a sorted order anyway, so the calls to sort() and reverse() are unnecessary. The algorithm only needs to figure out where the new child should be inserted. Re-sorting all children is entirely avoidable. Sort functions are often fast when the input is sorted already, but in this case because the sorting function is inverted, the computer always has to sort the entire list.nYou can't tell from looking at this method, but insertionsAfter nDespite CRDTs being the 'new hotness' in the collaborative editing game for years, I've been resisting them. As I said in my [recent blog post about CRDTs](https://josephg.com/blog/crdts-are-the-future/), they've been generally unworkable for real world collaborative editing because:nThey take up too much space on disk and in memory. (Automerge takes 1.1GB in RAM to store a 100kb document)nThey consume way too much CPU to process editsnUntil these issues are addressed, I can't recommend CRDTs for use in general computing.n'"),
|
||||
(0, 0, "'n'"),
|
||||
(1, 0, "'n'"),
|
||||
(2, 0, "'n'"),
|
||||
@@ -137990,4 +137990,4 @@ val data = #[(0, 0, "'Automerge is too slow and clunky. Martin (its principle ar
|
||||
(20698, 0, "'t'"),
|
||||
(20698, 1, "''"),
|
||||
(20698, 0, "'n'"),
|
||||
(20698, 1, "''")]
|
||||
(20698, 1, "''")]
|
||||
|
||||
Reference in New Issue
Block a user