Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

perf: remove immer from static report #501

Merged
merged 1 commit into from
Sep 14, 2023

Conversation

KuznetsovRoman
Copy link
Member

Что сделано

Избавление от immer в статическом отчете. Вместо immer, самостоятельно храним объект с правками. Так, state остается неизменным, а правки пишутся в объект diff. Затем новый стейт формируется из старого, на который наслоен diff. К этому механизму прилагаются три хелпера: applyStateUpdate (создание нового state из старого и diff), ensureDiffProperty (создание вложенного объекта при необходимости, используется при передаче diff в другую функцию), getUpdatedProperty (достает проп из state, учитывая, что он может быть перезаписан в diff).

В среднем, на больших отчетах задержка от immer при действиях - от 400 (если у пользователя мощное устройство и задействован только один тяжелый редьюсер (tree / grouped tests)) до 1600 (если у пользователя более слабое устройство и задействовано сразу два тяжелых редюсера)

Чтобы оценить, насколько избавление от immer ускоряет отчет на своем устройстве:

Отчет 1: https://nda.ya.ru/t/Wt4Uu-Mb6f4HVK
Отчет 2: https://nda.ya.ru/t/e1iRtpfT6f4HpB

Смотреть нужно на:

Выбор браузеров
Режим отображения тестов (failed/all/...)
Группировки
expand all / collapse all


export function groupMeta({group, groupKey, ...restArgs}) {
export function groupMeta({group, groupKey, diff = group, ...restArgs}) {
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Здесь и далее diff в функциях - место, куда нужно записывать изменения. По иерархии такие diff равны тому, чему устанавливаются по умолчанию (в данном случае, group)

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Довольно непонятное API. Если ничего не передал в diff значит изменения будут происходить на group.

При этом этот diff нужно в кучу утилит передавать и думать о том как его правильно сформировать и т.д. Выглядит максимально неудобно.

Не пробовал смотреть на immutableJS? Судя по performance графикам от immer - https://immerjs.github.io/immer/performance/ он довольно шустрый.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Если ничего не передал в diff значит изменения будут происходить на group

Да, так и задумывалось. Это опциональный аргумент "куда записывать изменения". По умолчанию, если не передать его, запишет в group, плюс, это напоминание, что туда нужно передавать

нужно думать о том, как его правильно сформировать

Да в принципе так же, как и обычное состояние. Просто изменения пишешь туда, и при передаче удостовериваешься, что передаваемый ключ существует с помощью ensureDiffProperty.

Не пробовал смотреть на ImmutableJS?

Пробовал, еще более неудобный

Comment on lines +23 to +26
group: group.byKey[groupKey],
result,
value,
diff: diff.byKey[groupKey]
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Так что дальше в функции они прокидываются так же, как и сами их read-only сущности из state

Comment on lines 26 to 27
case actionNames.INIT_GUI_REPORT:
case actionNames.INIT_STATIC_REPORT: {
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

В этом action, tree не является частью стейта, так что тут можно мутировать его напрямую, что гораздо проще по коду

@@ -17,80 +17,65 @@ import {ViewMode} from '../../../../constants/view-modes';
import {EXPAND_RETRIES} from '../../../../constants/expand-modes';
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Тут я перенес только action из статического отчета, потому что в gui профит слабый, да и логика там становится намного сложнее, так что оно того не стоит

Я пытался расположить обработку action в том же порядке, но тем не менее, в секции с reduce он отрезолвился плохо. Там можно не смотреть, оставил как было.

Редьюсер распилен на две части:

Первая (статика, без immer.produce):

switch (action.type) {
    case actionNames...
        ....
        return applyStateUpdate(state, diff)

Вторая (gui, с immer.produce):

return produce(state, draft => {
    switch (action.type) {
        case actionNames...
            ....
           break;
})`

Copy link
Member

@DudaGod DudaGod left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Мне не нравится текущий подход. Он получился довольно сложным и вербозным. Нужно думать о правильном формировании diff-а и таскать его за собой в каждую утилитку и формировать ее значение в ней. Это приводит к тому, что код становится трудно поддерживаемым, так как логика по формированию диффа размазана.

Я бы попробовал заюзать immutableJS - https://redux.gitbook.io/docs/recipes/usingimmutablejs. Судя по данным графикам - https://immerjs.github.io/immer/performance/ он работает существенно быстрее immera. И его использование не должно привезти к существенном усложнению кода.

return result;
}

function ensureDiffProperty(diff, path) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

А почему вместо этого нельзя просто юзать _.set из lodash. Он же тоже самое делает - _.set(diff, path, {})

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Нет, это две разные функции.

_.set - устанавливает значение.

_.set({a: {foo: 4, bar: 9}}, 'a', {}) вернет {a: {}}

Эта функция устанавливает значение только при необходимости

@@ -159,6 +159,40 @@ function preloadImage(url) {
new Image().src = url;
}

function applyStateUpdate(state, diff) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

А почему эти методы по работе со стейтом не вынести в отдельных utils/state? А то сейчас же мешанина из этих утилит.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Можно и вынести


export default (state, action) => {
switch (action.type) {
case actionNames.INIT_GUI_REPORT:
case actionNames.INIT_STATIC_REPORT: {
const {apiValues} = action.payload;

return applyChanges(state, apiValues);
return applyStateUpdate(state, {apiValues});
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

А почему просто не назвать updateState?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

потому что state не обновляется. Он иммутабелен, и updateState по сути ничего обновлять не будет. Тут именно создается новый стейт из старого и диффа


export function groupMeta({group, groupKey, ...restArgs}) {
export function groupMeta({group, groupKey, diff = group, ...restArgs}) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Довольно непонятное API. Если ничего не передал в diff значит изменения будут происходить на group.

При этом этот diff нужно в кучу утилит передавать и думать о том как его правильно сформировать и т.д. Выглядит максимально неудобно.

Не пробовал смотреть на immutableJS? Судя по performance графикам от immer - https://immerjs.github.io/immer/performance/ он довольно шустрый.

});
}

export function changeResultState(tree, resultId, state) {
changeNodeState(tree.results.stateById, resultId, state);
export function changeResultState(tree, resultId, state, diff = tree) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Скоро функция от кол-ва аргументов лопнет. Обычно стараемся при наличии 4 аргументов упаковывать их в объекты

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Упакую

@KuznetsovRoman KuznetsovRoman force-pushed the HERMIONE-1138.perf_remove_immer branch 3 times, most recently from 44c1b12 to 576919b Compare September 14, 2023 22:52
@KuznetsovRoman KuznetsovRoman force-pushed the HERMIONE-1138.perf_remove_immer branch from 576919b to 43ba93f Compare September 14, 2023 23:26
@KuznetsovRoman KuznetsovRoman merged commit f61b99a into master Sep 14, 2023
5 checks passed
@KuznetsovRoman KuznetsovRoman deleted the HERMIONE-1138.perf_remove_immer branch September 14, 2023 23:34
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants