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

feat!: update V8 flags and force optimization #41

Merged
merged 2 commits into from
Dec 11, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions packages/benchmark.js-plugin/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -196,6 +196,7 @@ async function runBenchmarks({
if (isAsync) {
await optimizeFunction(benchPayload);
await mongoMeasurement.start(uri);
global.gc?.();
await (async function __codspeed_root_frame__() {
Measurement.startInstrumentation();
await benchPayload();
Expand Down
2 changes: 0 additions & 2 deletions packages/core/src/index.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
import { checkV8Flags } from "./introspection";
import { MongoMeasurement } from "./mongoMeasurement";
import native_core from "./native_core";
import { initOptimization } from "./optimization";

declare const __VERSION__: string;

Expand All @@ -12,7 +11,6 @@ export const isBound = native_core.isBound;
export const mongoMeasurement = new MongoMeasurement();

export const setupCore = () => {
initOptimization();
native_core.Measurement.stopInstrumentation(
`Metadata: codspeed-node ${__VERSION__}`
);
Expand Down
4 changes: 4 additions & 0 deletions packages/core/src/introspection.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,10 @@ export const getV8Flags = () => {
"--predictable",
"--predictable-gc-schedule",
"--interpreted-frames-native-stack",
"--allow-natives-syntax",
"--expose-gc",
"--no-concurrent-sweeping",
"--max-old-space-size=4096",
];
if (nodeVersionMajor < 18) {
flags.push("--no-randomize-hashes");
Expand Down
37 changes: 14 additions & 23 deletions packages/core/src/optimization.ts
Original file line number Diff line number Diff line change
@@ -1,34 +1,25 @@
const skipOptimization = process.env.CODSPEED_FORCE_OPTIMIZATION !== "true";

export const initOptimization = () => {
if (!skipOptimization) {
// eslint-disable-next-line @typescript-eslint/no-var-requires
require("v8").setFlagsFromString("--allow-natives-syntax");
}
};

export const optimizeFunction = async (fn: CallableFunction) => {
if (skipOptimization) {
// warmup V8 symbols generation of the performance map
await fn();
return;
}
// Source: https://github.com/petkaantonov/bluebird/wiki/Optimization-killers#optimization-killers
await fn(); //Fill type-info
await fn(); // 2 calls are needed to go from uninitialized -> pre-monomorphic -> monomorphic
// a total of 7 calls seems to be the sweet spot
await fn();
await fn();
await fn();
await fn();
await fn();
await fn();
eval("%OptimizeFunctionOnNextCall(fn)");
await fn(); // optimize
};

export const optimizeFunctionSync = (fn: CallableFunction) => {
if (skipOptimization) {
// warmup V8 symbols generation of the performance map
fn();
return;
}
// Source: https://github.com/petkaantonov/bluebird/wiki/Optimization-killers#optimization-killers
fn(); //Fill type-info
fn(); // 2 calls are needed to go from uninitialized -> pre-monomorphic -> monomorphic
// a total of 7 calls seems to be the sweet spot
fn();
fn();
fn();
fn();
fn();
fn();
eval("%OptimizeFunctionOnNextCall(fn)");
fn(); // optimize
};
1 change: 1 addition & 0 deletions packages/tinybench-plugin/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,7 @@ export function withCodSpeed(bench: Bench): Bench {
await task.opts.beforeEach?.call(task);

await mongoMeasurement.start(uri);
global.gc?.();
await (async function __codspeed_root_frame__() {
Measurement.startInstrumentation();
await task.fn();
Expand Down
4 changes: 4 additions & 0 deletions packages/vitest-plugin/src/__tests__/index.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,10 @@ describe("codSpeedPlugin", () => {
"--predictable",
"--predictable-gc-schedule",
"--interpreted-frames-native-stack",
"--allow-natives-syntax",
"--expose-gc",
"--no-concurrent-sweeping",
"--max-old-space-size=4096",
"--no-scavenge-task",
],
},
Expand Down
23 changes: 11 additions & 12 deletions packages/vitest-plugin/src/__tests__/runner.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,23 +18,22 @@ const coreMocks = vi.hoisted(() => {
};
});

global.eval = vi.fn();

vi.mock("@codspeed/core", async (importOriginal) => {
const mod = await importOriginal<typeof import("@codspeed/core")>();
return { ...mod, ...coreMocks };
});

console.log = vi.fn();

vi.mock("vitest/suite", () => ({
getBenchFn: vi.fn(),
// wrapping the value in vi.fn(...) here will not work for some reason
getHooks: () => ({
beforeAll: [],
beforeEach: [],
afterEach: [],
afterAll: [],
}),
}));
vi.mock("vitest/suite", async (importOriginal) => {
const actual = await importOriginal<typeof import("vitest/suite")>();
return {
...actual,
getBenchFn: vi.fn(),
};
});
const mockedGetBenchFn = vi.mocked(getBenchFn);

describe("CodSpeedRunner", () => {
Expand Down Expand Up @@ -62,7 +61,7 @@ describe("CodSpeedRunner", () => {
"packages/vitest-plugin/src/__tests__/runner.test.ts::test bench"
);
expect(coreMocks.Measurement.startInstrumentation).toHaveBeenCalledTimes(1);
expect(benchFn).toHaveBeenCalledTimes(2);
expect(benchFn).toHaveBeenCalledTimes(8);
expect(coreMocks.Measurement.stopInstrumentation).toHaveBeenCalledTimes(1);
expect(coreMocks.mongoMeasurement.stop).toHaveBeenCalledTimes(1);
expect(console.log).toHaveBeenCalledWith(
Expand Down Expand Up @@ -116,7 +115,7 @@ describe("CodSpeedRunner", () => {
"packages/vitest-plugin/src/__tests__/runner.test.ts::nested suite::test bench"
);
expect(coreMocks.Measurement.startInstrumentation).toHaveBeenCalledTimes(1);
expect(benchFn).toHaveBeenCalledTimes(2);
expect(benchFn).toHaveBeenCalledTimes(8);
expect(coreMocks.Measurement.stopInstrumentation).toHaveBeenCalledTimes(1);
expect(coreMocks.mongoMeasurement.stop).toHaveBeenCalledTimes(1);
expect(console.log).toHaveBeenCalledWith(
Expand Down
7 changes: 4 additions & 3 deletions packages/vitest-plugin/src/runner.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,23 +15,23 @@ import { getBenchFn, getHooks } from "vitest/suite";
type SuiteHooks = ReturnType<typeof getHooks>;

function getSuiteHooks(suite: Suite, name: keyof SuiteHooks) {
return getHooks(suite)[name];
return getHooks(suite)?.[name] ?? [];
}

export async function callSuiteHook<T extends keyof SuiteHooks>(
suite: Suite,
currentTask: Task,
name: T
): Promise<void> {
if (name === "beforeEach" && suite.suite) {
if (name === "beforeEach" && suite?.suite) {
await callSuiteHook(suite.suite, currentTask, name);
}

const hooks = getSuiteHooks(suite, name);

await Promise.all(hooks.map((fn) => fn()));

if (name === "afterEach" && suite.suite) {
if (name === "afterEach" && suite?.suite) {
await callSuiteHook(suite.suite, currentTask, name);
}
}
Expand Down Expand Up @@ -69,6 +69,7 @@ async function runBench(benchmark: Benchmark, currentSuiteName: string) {

await callSuiteHook(benchmark.suite, benchmark, "beforeEach");
await mongoMeasurement.start(uri);
global.gc?.();
await (async function __codspeed_root_frame__() {
Measurement.startInstrumentation();
// @ts-expect-error we do not need to bind the function to an instance of tinybench's Bench
Expand Down
Loading