diff --git a/benchmark/react/lynx.config.js b/benchmark/react/lynx.config.js index 4f4df1d39c..feccce3d66 100644 --- a/benchmark/react/lynx.config.js +++ b/benchmark/react/lynx.config.js @@ -15,6 +15,7 @@ export default defineConfig({ jsOptions: { minimizerOptions: { mangle: false, + minify: false, }, }, }, @@ -28,7 +29,6 @@ export default defineConfig({ './cases/001-fib/index.ts', ], '002-hello-reactLynx': [ - 'event-target-polyfill', './src/patchProfile.ts', './cases/002-hello-reactLynx/index.tsx', ], diff --git a/benchmark/react/src/RunBenchmarkUntil.tsx b/benchmark/react/src/RunBenchmarkUntil.tsx index 2fbe655a3a..d8cb4f1e2c 100644 --- a/benchmark/react/src/RunBenchmarkUntil.tsx +++ b/benchmark/react/src/RunBenchmarkUntil.tsx @@ -2,6 +2,16 @@ // Licensed under the Apache License Version 2.0 that can be found in the // LICENSE file in the root directory of this source tree. +import { useEffect, useState } from '@lynx-js/react'; + export const RunBenchmarkUntilHydrate = () => { return ; }; + +export const RunBenchmarkUntilEffect = () => { + const [stopBenchmark, setStopBenchmark] = useState(false); + useEffect(() => { + setStopBenchmark(true); + }, []); + return ; +}; diff --git a/benchmark/react/src/patchProfile.ts b/benchmark/react/src/patchProfile.ts index 755f7da113..1544211679 100644 --- a/benchmark/react/src/patchProfile.ts +++ b/benchmark/react/src/patchProfile.ts @@ -4,37 +4,66 @@ import { hook } from './hook.js'; -if (__BACKGROUND__) { - // eslint-disable-next-line no-global-assign - console = { ...console }; -} - const PREFIX = __REPO_FILEPATH__.split('/').slice(0, -2).join('/'); const ignored: Record = {}; const stack: string[] = []; +let depth = 0; -hook(console, 'profile', (old, name) => { - old!(name); - if ( - (ignored[name!] ??= name === 'commitChanges' - || name!.startsWith('ReactLynx::diff::')) - ) { - stack.push('__IGNORED__'); - } else { - stack.push(name!); +if (typeof Codspeed !== 'undefined') { + function shouldIgnoreBenchmark(name: string | undefined) { + if ( + !name + || name === 'ReactLynx::commit' + || name === 'ReactLynx::commitChanges' + || name === 'ReactLynx::transferRoot' + || name === 'ReactLynx::BSI::setAttribute' + || name.startsWith('OnLifecycleEvent::') + || name.startsWith('ReactLynx::diff::') + || name.startsWith('ReactLynx::render::') + ) { + return true; + } + return false; } - Codspeed.startBenchmark(); -}); -hook(console, 'profileEnd', (old) => { - Codspeed.stopBenchmark(); - const name = stack.pop(); - if (name === '__IGNORED__') { - Codspeed.zeroStats(); - } else { - Codspeed.setExecutedBenchmark( - `${PREFIX}::${__webpack_chunkname__}-${name!}`, - ); - } - old!(); -}); + hook(lynx.performance, 'profileStart', (old, name, option) => { + old!.call(lynx.performance, name, option); + if ((ignored[name] ??= shouldIgnoreBenchmark(name))) { + stack.push('__IGNORED__'); + } else { + stack.push(name); + depth++; + if (depth > 1) { + console.log( + `Benchmark ${name} is ignored because it is nested, the stack: ${ + stack.join(', ') + }`, + ); + } else { + Codspeed.startBenchmark(); + } + } + }); + + hook(lynx.performance, 'profileEnd', (old) => { + const name = stack.pop()!; + if (name === '__IGNORED__') { + // Codspeed.zeroStats(); + } else { + if (depth > 1) { + // ignored + } else { + Codspeed.stopBenchmark(); + Codspeed.setExecutedBenchmark( + `${PREFIX}::${__webpack_chunkname__}-${ + name + .replace(/^ReactLynx::/, '') + .replace(/::/g, '__') + }`, + ); + } + depth--; + } + old!.call(lynx.performance); + }); +} diff --git a/packages/lynx/benchx_cli/scripts/build.mjs b/packages/lynx/benchx_cli/scripts/build.mjs index 3a308edce9..55d9e0cc36 100644 --- a/packages/lynx/benchx_cli/scripts/build.mjs +++ b/packages/lynx/benchx_cli/scripts/build.mjs @@ -30,7 +30,7 @@ console.log('noop') } const COMMIT = 'd6dd806293012c62e5104ad7ed2bed5c66f4f833'; -const PICK_COMMIT = '033e8243747fa0b3ffc01e4b2df321d73a30597f'; +const PICK_COMMIT = '1524ac02060bfc2e354e1865c989bc6f7a9882b1'; function checkCwd() { try { diff --git a/packages/react/runtime/src/backgroundSnapshot.ts b/packages/react/runtime/src/backgroundSnapshot.ts index f93d00b9cd..6f72835dc0 100644 --- a/packages/react/runtime/src/backgroundSnapshot.ts +++ b/packages/react/runtime/src/backgroundSnapshot.ts @@ -10,6 +10,7 @@ import type { Worklet } from '@lynx-js/react/worklet-runtime/bindings'; +import { profileEnd, profileStart } from './debug/utils.js'; import { processGestureBackground } from './gesture/processGestureBagkround.js'; import type { GestureKind } from './gesture/types.js'; import { diffArrayAction, diffArrayLepus } from './hydrate.js'; @@ -212,7 +213,7 @@ export class BackgroundSnapshotInstance { setAttribute(key: string | number, value: unknown): void { if (__PROFILE__) { - console.profile('setAttribute'); + profileStart('ReactLynx::BSI::setAttribute'); } if (key === 'values') { if (__globalSnapshotPatch) { @@ -260,7 +261,7 @@ export class BackgroundSnapshotInstance { } this.__values = value as unknown[]; if (__PROFILE__) { - console.profileEnd(); + profileEnd(); } return; } @@ -279,7 +280,7 @@ export class BackgroundSnapshotInstance { value, ); if (__PROFILE__) { - console.profileEnd(); + profileEnd(); } } diff --git a/packages/react/runtime/src/debug/utils.ts b/packages/react/runtime/src/debug/utils.ts new file mode 100644 index 0000000000..8fe37662f9 --- /dev/null +++ b/packages/react/runtime/src/debug/utils.ts @@ -0,0 +1,23 @@ +// Copyright 2024 The Lynx Authors. All rights reserved. +// Licensed under the Apache License Version 2.0 that can be found in the +// LICENSE file in the root directory of this source tree. + +/* eslint-disable @typescript-eslint/no-unnecessary-type-assertion */ + +const noop = () => {}; + +export const profileStart = /* @__PURE__ */ ((() => { + let p; + if (!(p = lynx.performance) || typeof p.profileStart !== 'function') { + return noop; + } + return p.profileStart.bind(p); +})()) as typeof lynx.performance.profileStart; + +export const profileEnd = /* @__PURE__ */ ((() => { + let p; + if (!(p = lynx.performance) || typeof p.profileEnd !== 'function') { + return noop; + } + return p.profileEnd.bind(p); +})()) as typeof lynx.performance.profileEnd; diff --git a/packages/react/runtime/src/lifecycle/destroy.ts b/packages/react/runtime/src/lifecycle/destroy.ts index ab21014241..39c24055a3 100644 --- a/packages/react/runtime/src/lifecycle/destroy.ts +++ b/packages/react/runtime/src/lifecycle/destroy.ts @@ -7,10 +7,11 @@ import { __root } from '../root.js'; import { delayedEvents } from './event/delayEvents.js'; import { delayedLifecycleEvents } from './event/delayLifecycleEvents.js'; import { globalCommitTaskMap } from './patch/commit.js'; +import { profileEnd, profileStart } from '../debug/utils.js'; function destroyBackground(): void { if (__PROFILE__) { - console.profile('destroyBackground'); + profileStart('ReactLynx::destroyBackground'); } // eslint-disable-next-line @typescript-eslint/no-unsafe-argument @@ -27,7 +28,7 @@ function destroyBackground(): void { delayedEvents.length = 0; } if (__PROFILE__) { - console.profileEnd(); + profileEnd(); } } diff --git a/packages/react/runtime/src/lifecycle/event/jsReady.ts b/packages/react/runtime/src/lifecycle/event/jsReady.ts index 9bed196a2b..526451b866 100644 --- a/packages/react/runtime/src/lifecycle/event/jsReady.ts +++ b/packages/react/runtime/src/lifecycle/event/jsReady.ts @@ -1,6 +1,7 @@ // Copyright 2025 The Lynx Authors. All rights reserved. // Licensed under the Apache License Version 2.0 that can be found in the // LICENSE file in the root directory of this source tree. +import { profileEnd, profileStart } from '../../debug/utils.js'; import { LifecycleConstant } from '../../lifecycleConstant.js'; import { __root } from '../../root.js'; @@ -9,13 +10,25 @@ let jsReadyEventIdSwap: Record; function jsReady(): void { isJSReady = true; + + if (__PROFILE__) { + profileStart('ReactLynx::transferRoot'); + profileStart('ReactLynx::serializeRoot'); + } + const root = JSON.stringify(__root); + if (__PROFILE__) { + profileEnd(); + } __OnLifecycleEvent([ LifecycleConstant.firstScreen, /* FIRST_SCREEN */ { - root: JSON.stringify(__root), + root, jsReadyEventIdSwap, }, ]); + if (__PROFILE__) { + profileEnd(); + } jsReadyEventIdSwap = {}; } diff --git a/packages/react/runtime/src/lifecycle/patch/commit.ts b/packages/react/runtime/src/lifecycle/patch/commit.ts index b85df21440..67cac9ba55 100644 --- a/packages/react/runtime/src/lifecycle/patch/commit.ts +++ b/packages/react/runtime/src/lifecycle/patch/commit.ts @@ -31,6 +31,7 @@ import { takeWorkletRefInitValuePatch } from '../../worklet/workletRefPool.js'; import { getReloadVersion } from '../pass.js'; import type { SnapshotPatch } from './snapshotPatch.js'; import { takeGlobalSnapshotPatch } from './snapshotPatch.js'; +import { profileEnd, profileStart } from '../../debug/utils.js'; let globalFlushOptions: FlushOptions = {}; function takeGlobalFlushOptions() { @@ -181,7 +182,7 @@ function commitPatchUpdate(patchList: PatchList, patchOptions: GlobalPatchOption // console.debug('commitPatchUpdate:', prettyFormatSnapshotPatch(patchList.patchList[0]?.snapshotPatch)); if (__PROFILE__) { - console.profile('commitChanges'); + profileStart('ReactLynx::commitChanges'); } markTiming('packChangesStart'); const obj: { @@ -200,7 +201,7 @@ function commitPatchUpdate(patchList: PatchList, patchOptions: GlobalPatchOption setPipeline(undefined); } if (__PROFILE__) { - console.profileEnd(); + profileEnd(); } return obj; diff --git a/packages/react/runtime/src/lifecycle/reload.ts b/packages/react/runtime/src/lifecycle/reload.ts index ed2760d838..6a973d53fe 100644 --- a/packages/react/runtime/src/lifecycle/reload.ts +++ b/packages/react/runtime/src/lifecycle/reload.ts @@ -23,10 +23,11 @@ import { increaseReloadVersion } from './pass.js'; import { deinitGlobalSnapshotPatch } from './patch/snapshotPatch.js'; import { shouldDelayUiOps } from './ref/delay.js'; import { renderMainThread } from './render.js'; +import { profileEnd, profileStart } from '../debug/utils.js'; function reloadMainThread(data: unknown, options: UpdatePageOption): void { if (__PROFILE__) { - console.profile('reloadTemplate'); + profileStart('ReactLynx::reloadMainThread'); } increaseReloadVersion(); @@ -64,14 +65,14 @@ function reloadMainThread(data: unknown, options: UpdatePageOption): void { __FlushElementTree(__page, options); if (__PROFILE__) { - console.profileEnd(); + profileEnd(); } return; } function reloadBackground(updateData: Record): void { if (__PROFILE__) { - console.profile('reload'); + profileStart('ReactLynx::reloadBackground'); } deinitGlobalSnapshotPatch(); @@ -88,7 +89,7 @@ function reloadBackground(updateData: Record): void { render(__root.__jsx, __root as any); if (__PROFILE__) { - console.profileEnd(); + profileEnd(); } } diff --git a/packages/react/runtime/src/lifecycle/render.ts b/packages/react/runtime/src/lifecycle/render.ts index b2abb12d3a..e3b50106c6 100644 --- a/packages/react/runtime/src/lifecycle/render.ts +++ b/packages/react/runtime/src/lifecycle/render.ts @@ -8,6 +8,7 @@ import { isValidElement } from 'preact'; +import { profileEnd, profileStart } from '../debug/utils.js'; import { renderOpcodesInto } from '../opcodes.js'; import { render as renderToString } from '../renderToOpcodes/index.js'; import { __root } from '../root.js'; @@ -17,7 +18,7 @@ function renderMainThread(): void { let opcodes; try { if (__PROFILE__) { - console.profile('renderToString'); + profileStart('ReactLynx::renderMainThread'); } opcodes = renderToString(__root.__jsx, undefined); } catch (e) { @@ -25,7 +26,7 @@ function renderMainThread(): void { opcodes = []; } finally { if (__PROFILE__) { - console.profileEnd(); + profileEnd(); } } @@ -40,7 +41,7 @@ function renderMainThread(): void { } if (__PROFILE__) { - console.profile('renderOpcodesInto'); + profileStart('ReactLynx::renderOpcodes'); } // eslint-disable-next-line @typescript-eslint/no-unsafe-argument renderOpcodesInto(opcodes, __root as any); @@ -48,7 +49,7 @@ function renderMainThread(): void { __root.__opcodes = opcodes; } if (__PROFILE__) { - console.profileEnd(); + profileEnd(); } } diff --git a/packages/react/runtime/src/lynx-api.ts b/packages/react/runtime/src/lynx-api.ts index 71f17c9213..185e116b01 100644 --- a/packages/react/runtime/src/lynx-api.ts +++ b/packages/react/runtime/src/lynx-api.ts @@ -7,6 +7,7 @@ import { useState } from 'preact/hooks'; import type { Consumer, FC, ReactNode } from 'react'; import { factory, withInitDataInState } from './compat/initData.js'; +import { profileEnd, profileStart } from './debug/utils.js'; import { useLynxGlobalEventListener } from './hooks/useLynxGlobalEventListener.js'; import { LifecycleConstant } from './lifecycleConstant.js'; import { flushDelayedLifecycleEvents } from './lynx/tt.js'; @@ -94,8 +95,14 @@ export const root: Root = { preactProcess = cb; }; try { + if (__PROFILE__) { + profileStart('ReactLynx::renderBackground'); + } // eslint-disable-next-line @typescript-eslint/no-unsafe-argument render(jsx, __root as any); + if (__PROFILE__) { + profileEnd(); + } (preactProcess as (() => void) | undefined)?.(); } finally { options.debounceRendering = oldDebounceRendering!; diff --git a/packages/react/runtime/src/lynx/env.ts b/packages/react/runtime/src/lynx/env.ts index 519698de7d..616942b9fb 100644 --- a/packages/react/runtime/src/lynx/env.ts +++ b/packages/react/runtime/src/lynx/env.ts @@ -1,6 +1,7 @@ // Copyright 2024 The Lynx Authors. All rights reserved. // Licensed under the Apache License Version 2.0 that can be found in the // LICENSE file in the root directory of this source tree. +import { profileEnd, profileStart } from '../debug/utils.js'; import type { DataProcessorDefinition, InitData, InitDataRaw } from '../lynx-api.js'; export function setupLynxEnv(): void { @@ -51,7 +52,7 @@ export function setupLynxEnv(): void { let hasDefaultDataProcessorExecuted = false; globalThis.processData = (data, processorName) => { if (__PROFILE__) { - console.profile('processData'); + profileStart('processData'); } let r: InitData | InitDataRaw; @@ -70,7 +71,7 @@ export function setupLynxEnv(): void { } if (__PROFILE__) { - console.profileEnd(); + profileEnd(); } if (hasDefaultDataProcessorExecuted === false) { diff --git a/packages/react/runtime/src/lynx/tt.ts b/packages/react/runtime/src/lynx/tt.ts index 51a3b6cdfe..1cc0f84e94 100644 --- a/packages/react/runtime/src/lynx/tt.ts +++ b/packages/react/runtime/src/lynx/tt.ts @@ -8,6 +8,7 @@ import type { FirstScreenData } from '../lifecycleConstant.js'; import { PerformanceTimingFlags, PipelineOrigins, beginPipeline, markTiming } from './performance.js'; import { BackgroundSnapshotInstance, hydrate } from '../backgroundSnapshot.js'; import { runWithForce } from './runWithForce.js'; +import { profileEnd, profileStart } from '../debug/utils.js'; import { destroyBackground } from '../lifecycle/destroy.js'; import { delayedEvents, delayedPublishEvent } from '../lifecycle/event/delayEvents.js'; import { delayLifecycleEvent, delayedLifecycleEvents } from '../lifecycle/event/delayLifecycleEvents.js'; @@ -52,7 +53,7 @@ function onLifecycleEvent([type, data]: [LifecycleConstant, unknown]) { } if (__PROFILE__) { - console.profile(`OnLifecycleEvent::${type}`); + profileStart(`OnLifecycleEvent::${type}`); } try { @@ -62,7 +63,7 @@ function onLifecycleEvent([type, data]: [LifecycleConstant, unknown]) { } if (__PROFILE__) { - console.profileEnd(); + profileEnd(); } } @@ -71,7 +72,7 @@ function onLifecycleEventImpl(type: LifecycleConstant, data: unknown): void { case LifecycleConstant.firstScreen: { const { root: lepusSide, jsReadyEventIdSwap } = data as FirstScreenData; if (__PROFILE__) { - console.profile('hydrate'); + profileStart('ReactLynx::hydrate'); } beginPipeline(true, PipelineOrigins.reactLynxHydrate, PerformanceTimingFlags.reactLynxHydrate); markTiming('hydrateParseSnapshotStart'); @@ -83,7 +84,7 @@ function onLifecycleEventImpl(type: LifecycleConstant, data: unknown): void { __root as BackgroundSnapshotInstance, ); if (__PROFILE__) { - console.profileEnd(); + profileEnd(); } markTiming('diffVdomEnd'); @@ -109,9 +110,6 @@ function onLifecycleEventImpl(type: LifecycleConstant, data: unknown): void { // console.debug("********** After hydration:"); // printSnapshotInstance(__root as BackgroundSnapshotInstance); - if (__PROFILE__) { - console.profile('commitChanges'); - } const commitTaskId = genCommitTaskId(); const patchList: PatchList = { patchList: [{ snapshotPatch, id: commitTaskId }], diff --git a/packages/react/runtime/src/snapshot/gesture.ts b/packages/react/runtime/src/snapshot/gesture.ts index 3927a2d02e..8f1307d7e1 100644 --- a/packages/react/runtime/src/snapshot/gesture.ts +++ b/packages/react/runtime/src/snapshot/gesture.ts @@ -16,15 +16,9 @@ export function updateGesture( if (!snapshot.__elements) { return; } - if (__PROFILE__) { - console.profile('updateGesture'); - } const value = snapshot.__values![expIndex] as GestureKind; if (workletType === 'main-thread') { processGesture(snapshot.__elements[elementIndex]!, value, oldValue as GestureKind, isMainThreadHydrating); } - if (__PROFILE__) { - console.profileEnd(); - } } diff --git a/packages/react/runtime/vitest.config.ts b/packages/react/runtime/vitest.config.ts index a363785a67..5b0ce2a1be 100644 --- a/packages/react/runtime/vitest.config.ts +++ b/packages/react/runtime/vitest.config.ts @@ -88,6 +88,7 @@ export default defineConfig({ 'src/root.ts', 'src/debug/component-stack.ts', 'src/debug/debug.ts', + 'src/debug/utils.ts', 'src/lynx/calledByNative.ts', 'src/lynx/component.ts', 'src/lynx/dynamic-js.ts',