-
Notifications
You must be signed in to change notification settings - Fork 38
improve printing algorithm performance for many nested callbacks #576
Conversation
refactor `CustomLayout t list` to `CustomLayout (t Lazy.t) list`
Removing the |
The problem is that the way Can the two cases that |
Prettier: let foo = function (x, y, z) {
foo(x, y, z);
};
let foo = function (x, y, z) {
foo(x, y, function (x, y, z) {
foo(x, y, z);
});
};
let foo = function (x, y, z) {
foo(x, y, function (x, y, z) {
foo(x, y, function (x, y, z) {
foo(x, y, z);
});
});
};
let foo = function (x, y, z) {
foo(x, y, function (x, y, z) {
foo(x, y, function (x, y, z) {
foo(x, y, function (x, y, z) {
foo(x, y, z);
});
});
});
};
let foo1 = (x, y, z) => foo(x, y, z);
let foo2 = (x, y, z) => foo1(x, y, (x, y, z) => foo(x, y, z));
let foo3 = (x, y, z) =>
foo2(x, y, (x, y, z) => foo1(x, y, (x, y, z) => foo(x, y, z)));
let foo4 = (x, y, z) =>
foo3(x, y, (x, y, z) =>
foo2(x, y, (x, y, z) => foo1(x, y, (x, y, z) => foo(x, y, z)))
);
let foo5 = (x, y, z) =>
foo4(x, y, (x, y, z) =>
foo3(x, y, (x, y, z) =>
foo2(x, y, (x, y, z) => foo1(x, y, (x, y, z) => foo(x, y, z)))
)
);
let foo1 = (x, y, z) => {
foo(x, y, z);
};
let foo2 = (x, y, z) => {
foo1(x, y, (x, y, z) => {
foo(x, y, z);
});
};
let foo3 = (x, y, z) => {
foo2(x, y, (x, y, z) => {
foo1(x, y, (x, y, z) => {
foo(x, y, z);
});
});
};
let foo4 = (x, y, z) => {
foo3(x, y, (x, y, z) => {
foo2(x, y, (x, y, z) => {
foo1(x, y, (x, y, z) => {
foo(x, y, z);
});
});
});
}; Rescript: let foo1 = (x, y, z) => foo(x, y, z)
let foo2 = (x, y, z) => foo1(x, y, (x, y, z) => foo(x, y, z))
let foo3 = (x, y, z) => foo2(x, y, (x, y, z) => foo1(x, y, (x, y, z) => foo(x, y, z)))
let foo4 = (x, y, z) =>
foo3(x, y, (x, y, z) => foo2(x, y, (x, y, z) => foo1(x, y, (x, y, z) => foo(x, y, z))))
let foo5 = (x, y, z) =>
foo4(x, y, (x, y, z) =>
foo3(x, y, (x, y, z) => foo2(x, y, (x, y, z) => foo1(x, y, (x, y, z) => foo(x, y, z))))
)
let foo1 = (x, y, z) => {
foo(x, y, z)
}
let foo2 = (x, y, z) => {
foo1(x, y, (x, y, z) => {
foo(x, y, z)
})
}
let foo3 = (x, y, z) => {
foo2(x, y, (x, y, z) => {
foo1(x, y, (x, y, z) => {
foo(x, y, z)
})
})
}
let foo4 = (x, y, z) => {
foo3(x, y, (x, y, z) => {
foo2(x, y, (x, y, z) => {
foo1(x, y, (x, y, z) => {
foo(x, y, z)
})
})
})
} |
On the example Prettier: let foo = () =>
bar((x) =>
bar((x) =>
bar((x) =>
bar((x) =>
bar((x) =>
bar((x) =>
bar((x) =>
bar((x) => bar((x) => bar((x) => bar((x) => bar((x) => x)))))
)
)
)
)
)
)
); ReScript: let foo = () =>
bar(x =>
bar(x =>
bar(x =>
bar(x => bar(x => bar(x => bar(x => bar(x => bar(x => bar(x => bar(x => bar(x => x)))))))))
)
)
) |
So first observation is: prettier might be using 2 things not 3? Possible. Second observation: the state exploration does not seem to need to be complete (i.e. 2 or 3, to the power of n). In the following sense:
So you don't get e.g. a stack of (breaks, doesn't, breaks, doesn't). In that sense, the semantics of How does that sound? |
To be precise,
but:
|
Sounds good to me. Is that a complex change? |
Don't know, one needs to try. |
I think likely only the |
This is what I would try. Permitted sequences have the form 1 1 1 1 1 2 2 2 Suppose there are at most 2 cases. Keep the current If the current level is
If the current level is
This generalises to level There can be different instances of |
How does your suggestion solve the performance hit incurred by scanning the whole tree in Maybe |
I had a vague memory that was not an issue. Just checked the code:
can't see any slowdown there. |
But I'm not sure I understood your question. |
Take it with a grain of salt, done a quick back-of-the-envelope calculation. But the difference, given exactly the same grammar we have today, is from |
…ustomLayout variations
Walking the I now brought back the Performance:
Compared to ~22 seconds before lazy evaluation and the The formatting difference (hence failed tests): diff --git a/tests/printer/other/expected/nesting.res.txt b/tests/printer/other/expected/nesting.res.txt
index 0cafca836cfc..b25b2d1724cd 100644
--- a/tests/printer/other/expected/nesting.res.txt
+++ b/tests/printer/other/expected/nesting.res.txt
@@ -1,7 +1,5 @@
-let unitsCommands = state.units->Js.Array2.mapi((
- {unit: targetUnit, coordinates: targetCoordinates},
- i,
-) => {
+let unitsCommands =
+ state.units->Js.Array2.mapi(({unit: targetUnit, coordinates: targetCoordinates}, i) => {
// n^2
let res = []
state.units->Js.Array2.forEachi((
@@ -50,4 +48,4 @@ let unitsCommands = state.units->Js.Array2.mapi((
}
})
res
-})
+ }) The diff ignores whitespace changes, the whole function body is a single indention deeper than before. WDYT? |
Tried the latest version here. I've made the example slightly bigger:
|
With just another 3 more nested callbacks, it uses up nearly all the memory on my machine and takes 57 seconds. |
The first issue is that it generates an exponential number of docs, even before trying to format them. |
Turns out parsing also takes time on a bigger nested example. So that time should be factored out of the measurement. |
The 2 cases produced in |
To summarise my understanding. The current PR puts the real logic behind lazy, so that printing can do more work. It has gotten rid of a 3^n explosion but there's still a 2^n explosion left at doc construction time. Perhaps it's possible to patch this PR to remove the 2^n. |
One step forward, in the lazy approach, here: #590 Another approach consists of not generating at all an exponential number of docs (whether hidden behind lazy or not), but only generate those corresponding to the sequences: #576 (comment) |
My thoughts:
WDYT? |
By output is still not the desired one, do you mean the final formatting being printed, or timing/code structure? |
It's fast but it does not print what we want (e.g. the example motivating this, but also changes to the tests, need to check if they are good or not). More experimentation needed. |
This prints what we need: | CustomLayout children ->
let _ = List.fold_left
(fun forceBreak child ->
let childForcesBreak = walk (Lazy.force child) in
forceBreak || childForcesBreak)
false children in
false but it is slow, as it undoes all what lazy does. Btw this is the output: let foo = () => bar(x => bar(x => bar(x => bar(x => bar(x => bar(x => bar(x => bar(x => bar(x =>
bar(x => bar(x => bar(x => bar(x => bar(x => x)))))
))))))))) It corresponds to what TS does. Except for all the closing parens in one place. |
Now #590 propagates the force break later on. |
The idea is to pass an extra parameter to all the functions that produce a doc. So all the printing functions. |
And here it is in the simplest version: |
Suggested implementation, with a caveat of me know being familiar with the code at all :-)
I have completely removed
propagateForcedBreaks
from the code as it looked to me to be doing nothing while forcing evaluation of everything. Did I miss something?See #261.