mirror of
https://github.com/oven-sh/bun
synced 2026-02-04 07:58:54 +00:00
Compare commits
2 Commits
dylan/pyth
...
jarred/ben
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
25381f1ad2 | ||
|
|
806e253a89 |
@@ -63,6 +63,7 @@ include(GenerateDependencyVersions)
|
||||
# --- Targets ---
|
||||
|
||||
include(BuildBun)
|
||||
include(BuildMisctools)
|
||||
|
||||
# --- Analysis ---
|
||||
|
||||
|
||||
449
cmake/targets/BuildMisctools.cmake
Normal file
449
cmake/targets/BuildMisctools.cmake
Normal file
@@ -0,0 +1,449 @@
|
||||
# Misctools - standalone utilities for testing and benchmarking
|
||||
|
||||
# cold-jsc-start: A minimal JSC cold start benchmark tool
|
||||
# This tool measures JSC initialization overhead.
|
||||
#
|
||||
# Build with: cmake --build build/release --target cold-jsc-start
|
||||
# Usage: ./cold-jsc-start -e "write('hello')"
|
||||
# ./cold-jsc-start <file.js>
|
||||
|
||||
if(NOT WIN32 AND NOT BUN_LINK_ONLY AND NOT BUN_CPP_ONLY)
|
||||
set(COLD_JSC_WEBKIT_PATH ${WEBKIT_PATH})
|
||||
|
||||
if(EXISTS ${COLD_JSC_WEBKIT_PATH}/lib/libJavaScriptCore.a)
|
||||
message(STATUS "cold-jsc-start will use WebKit from: ${COLD_JSC_WEBKIT_PATH}")
|
||||
|
||||
add_executable(cold-jsc-start EXCLUDE_FROM_ALL
|
||||
${CWD}/misctools/cold-jsc-start.cpp
|
||||
)
|
||||
|
||||
set_target_properties(cold-jsc-start PROPERTIES
|
||||
CXX_STANDARD 23
|
||||
CXX_STANDARD_REQUIRED YES
|
||||
CXX_EXTENSIONS YES
|
||||
CXX_VISIBILITY_PRESET hidden
|
||||
VISIBILITY_INLINES_HIDDEN YES
|
||||
INCLUDE_DIRECTORIES ""
|
||||
)
|
||||
|
||||
# Use same include directories as bun
|
||||
target_include_directories(cold-jsc-start BEFORE PRIVATE
|
||||
${COLD_JSC_WEBKIT_PATH}/include
|
||||
${CWD}/src/bun.js/bindings
|
||||
)
|
||||
|
||||
# Use same compile definitions as bun
|
||||
target_compile_definitions(cold-jsc-start PRIVATE
|
||||
_HAS_EXCEPTIONS=0
|
||||
BUILDING_JSCONLY__
|
||||
STATICALLY_LINKED_WITH_JavaScriptCore=1
|
||||
STATICALLY_LINKED_WITH_BMALLOC=1
|
||||
BUILDING_WITH_CMAKE=1
|
||||
JSC_OBJC_API_ENABLED=0
|
||||
USE_BUN_JSC_ADDITIONS=1
|
||||
)
|
||||
|
||||
# Use same compile options as bun (from BuildBun.cmake)
|
||||
if(NOT WIN32)
|
||||
target_compile_options(cold-jsc-start PRIVATE
|
||||
-fconstexpr-steps=2542484
|
||||
-fconstexpr-depth=54
|
||||
-fno-pic
|
||||
-fno-pie
|
||||
-faddrsig
|
||||
)
|
||||
endif()
|
||||
|
||||
target_link_libraries(cold-jsc-start PRIVATE
|
||||
${COLD_JSC_WEBKIT_PATH}/lib/libJavaScriptCore.a
|
||||
${COLD_JSC_WEBKIT_PATH}/lib/libWTF.a
|
||||
)
|
||||
|
||||
if(EXISTS ${COLD_JSC_WEBKIT_PATH}/lib/libbmalloc.a)
|
||||
target_link_libraries(cold-jsc-start PRIVATE ${COLD_JSC_WEBKIT_PATH}/lib/libbmalloc.a)
|
||||
endif()
|
||||
|
||||
if(APPLE)
|
||||
target_link_libraries(cold-jsc-start PRIVATE icucore resolv)
|
||||
target_compile_definitions(cold-jsc-start PRIVATE
|
||||
U_DISABLE_RENAMING=1
|
||||
_DARWIN_NON_CANCELABLE=1
|
||||
)
|
||||
target_link_options(cold-jsc-start PRIVATE
|
||||
-Wl,-ld_new
|
||||
-Wl,-no_compact_unwind
|
||||
-Wl,-w
|
||||
-fno-keep-static-consts
|
||||
)
|
||||
endif()
|
||||
|
||||
if(LINUX)
|
||||
target_link_libraries(cold-jsc-start PRIVATE
|
||||
${COLD_JSC_WEBKIT_PATH}/lib/libicui18n.a
|
||||
${COLD_JSC_WEBKIT_PATH}/lib/libicuuc.a
|
||||
${COLD_JSC_WEBKIT_PATH}/lib/libicudata.a
|
||||
pthread
|
||||
dl
|
||||
)
|
||||
target_link_options(cold-jsc-start PRIVATE -no-pie)
|
||||
if(USE_STATIC_LIBATOMIC)
|
||||
target_link_libraries(cold-jsc-start PRIVATE libatomic.a)
|
||||
else()
|
||||
target_link_libraries(cold-jsc-start PRIVATE atomic)
|
||||
endif()
|
||||
endif()
|
||||
# bench-jsc-e2e: Single E2E cold start benchmark
|
||||
add_executable(bench-jsc-e2e EXCLUDE_FROM_ALL
|
||||
${CWD}/misctools/bench-jsc-e2e.cpp
|
||||
)
|
||||
set_target_properties(bench-jsc-e2e PROPERTIES
|
||||
CXX_STANDARD 23
|
||||
CXX_STANDARD_REQUIRED YES
|
||||
CXX_EXTENSIONS YES
|
||||
CXX_VISIBILITY_PRESET hidden
|
||||
VISIBILITY_INLINES_HIDDEN YES
|
||||
INCLUDE_DIRECTORIES ""
|
||||
)
|
||||
target_include_directories(bench-jsc-e2e BEFORE PRIVATE
|
||||
${COLD_JSC_WEBKIT_PATH}/include
|
||||
${CWD}/src/bun.js/bindings
|
||||
)
|
||||
target_compile_definitions(bench-jsc-e2e PRIVATE
|
||||
_HAS_EXCEPTIONS=0
|
||||
BUILDING_JSCONLY__
|
||||
STATICALLY_LINKED_WITH_JavaScriptCore=1
|
||||
STATICALLY_LINKED_WITH_BMALLOC=1
|
||||
BUILDING_WITH_CMAKE=1
|
||||
JSC_OBJC_API_ENABLED=0
|
||||
USE_BUN_JSC_ADDITIONS=1
|
||||
)
|
||||
if(NOT WIN32)
|
||||
target_compile_options(bench-jsc-e2e PRIVATE
|
||||
-fconstexpr-steps=2542484
|
||||
-fconstexpr-depth=54
|
||||
-fno-pic
|
||||
-fno-pie
|
||||
-faddrsig
|
||||
)
|
||||
endif()
|
||||
target_link_libraries(bench-jsc-e2e PRIVATE
|
||||
${COLD_JSC_WEBKIT_PATH}/lib/libJavaScriptCore.a
|
||||
${COLD_JSC_WEBKIT_PATH}/lib/libWTF.a
|
||||
)
|
||||
if(EXISTS ${COLD_JSC_WEBKIT_PATH}/lib/libbmalloc.a)
|
||||
target_link_libraries(bench-jsc-e2e PRIVATE ${COLD_JSC_WEBKIT_PATH}/lib/libbmalloc.a)
|
||||
endif()
|
||||
if(APPLE)
|
||||
target_link_libraries(bench-jsc-e2e PRIVATE icucore resolv)
|
||||
target_compile_definitions(bench-jsc-e2e PRIVATE
|
||||
U_DISABLE_RENAMING=1
|
||||
_DARWIN_NON_CANCELABLE=1
|
||||
)
|
||||
target_link_options(bench-jsc-e2e PRIVATE
|
||||
-Wl,-ld_new
|
||||
-Wl,-no_compact_unwind
|
||||
-Wl,-w
|
||||
-fno-keep-static-consts
|
||||
)
|
||||
endif()
|
||||
if(LINUX)
|
||||
target_link_libraries(bench-jsc-e2e PRIVATE
|
||||
${COLD_JSC_WEBKIT_PATH}/lib/libicui18n.a
|
||||
${COLD_JSC_WEBKIT_PATH}/lib/libicuuc.a
|
||||
${COLD_JSC_WEBKIT_PATH}/lib/libicudata.a
|
||||
pthread dl atomic
|
||||
)
|
||||
target_link_options(bench-jsc-e2e PRIVATE -no-pie)
|
||||
endif()
|
||||
|
||||
# bench-jsc-100-e2e: 100 VMs + GlobalObjects + eval
|
||||
add_executable(bench-jsc-100-e2e EXCLUDE_FROM_ALL
|
||||
${CWD}/misctools/bench-jsc-100-e2e.cpp
|
||||
)
|
||||
set_target_properties(bench-jsc-100-e2e PROPERTIES
|
||||
CXX_STANDARD 23
|
||||
CXX_STANDARD_REQUIRED YES
|
||||
CXX_EXTENSIONS YES
|
||||
CXX_VISIBILITY_PRESET hidden
|
||||
VISIBILITY_INLINES_HIDDEN YES
|
||||
INCLUDE_DIRECTORIES ""
|
||||
)
|
||||
target_include_directories(bench-jsc-100-e2e BEFORE PRIVATE
|
||||
${COLD_JSC_WEBKIT_PATH}/include
|
||||
${CWD}/src/bun.js/bindings
|
||||
)
|
||||
target_compile_definitions(bench-jsc-100-e2e PRIVATE
|
||||
_HAS_EXCEPTIONS=0
|
||||
BUILDING_JSCONLY__
|
||||
STATICALLY_LINKED_WITH_JavaScriptCore=1
|
||||
STATICALLY_LINKED_WITH_BMALLOC=1
|
||||
BUILDING_WITH_CMAKE=1
|
||||
JSC_OBJC_API_ENABLED=0
|
||||
USE_BUN_JSC_ADDITIONS=1
|
||||
)
|
||||
if(NOT WIN32)
|
||||
target_compile_options(bench-jsc-100-e2e PRIVATE
|
||||
-fconstexpr-steps=2542484
|
||||
-fconstexpr-depth=54
|
||||
-fno-pic
|
||||
-fno-pie
|
||||
-faddrsig
|
||||
)
|
||||
endif()
|
||||
target_link_libraries(bench-jsc-100-e2e PRIVATE
|
||||
${COLD_JSC_WEBKIT_PATH}/lib/libJavaScriptCore.a
|
||||
${COLD_JSC_WEBKIT_PATH}/lib/libWTF.a
|
||||
)
|
||||
if(EXISTS ${COLD_JSC_WEBKIT_PATH}/lib/libbmalloc.a)
|
||||
target_link_libraries(bench-jsc-100-e2e PRIVATE ${COLD_JSC_WEBKIT_PATH}/lib/libbmalloc.a)
|
||||
endif()
|
||||
if(APPLE)
|
||||
target_link_libraries(bench-jsc-100-e2e PRIVATE icucore resolv)
|
||||
target_compile_definitions(bench-jsc-100-e2e PRIVATE
|
||||
U_DISABLE_RENAMING=1
|
||||
_DARWIN_NON_CANCELABLE=1
|
||||
)
|
||||
target_link_options(bench-jsc-100-e2e PRIVATE
|
||||
-Wl,-ld_new
|
||||
-Wl,-no_compact_unwind
|
||||
-Wl,-w
|
||||
-fno-keep-static-consts
|
||||
)
|
||||
endif()
|
||||
if(LINUX)
|
||||
target_link_libraries(bench-jsc-100-e2e PRIVATE
|
||||
${COLD_JSC_WEBKIT_PATH}/lib/libicui18n.a
|
||||
${COLD_JSC_WEBKIT_PATH}/lib/libicuuc.a
|
||||
${COLD_JSC_WEBKIT_PATH}/lib/libicudata.a
|
||||
pthread dl atomic
|
||||
)
|
||||
target_link_options(bench-jsc-100-e2e PRIVATE -no-pie)
|
||||
endif()
|
||||
|
||||
# bench-jsc-multi-eval: Multi-script eval benchmark
|
||||
add_executable(bench-jsc-multi-eval EXCLUDE_FROM_ALL
|
||||
${CWD}/misctools/bench-jsc-multi-eval.cpp
|
||||
)
|
||||
set_target_properties(bench-jsc-multi-eval PROPERTIES
|
||||
CXX_STANDARD 23
|
||||
CXX_STANDARD_REQUIRED YES
|
||||
CXX_EXTENSIONS YES
|
||||
CXX_VISIBILITY_PRESET hidden
|
||||
VISIBILITY_INLINES_HIDDEN YES
|
||||
INCLUDE_DIRECTORIES ""
|
||||
)
|
||||
target_include_directories(bench-jsc-multi-eval BEFORE PRIVATE
|
||||
${COLD_JSC_WEBKIT_PATH}/include
|
||||
${CWD}/src/bun.js/bindings
|
||||
)
|
||||
target_compile_definitions(bench-jsc-multi-eval PRIVATE
|
||||
_HAS_EXCEPTIONS=0
|
||||
BUILDING_JSCONLY__
|
||||
STATICALLY_LINKED_WITH_JavaScriptCore=1
|
||||
STATICALLY_LINKED_WITH_BMALLOC=1
|
||||
BUILDING_WITH_CMAKE=1
|
||||
JSC_OBJC_API_ENABLED=0
|
||||
USE_BUN_JSC_ADDITIONS=1
|
||||
)
|
||||
if(NOT WIN32)
|
||||
target_compile_options(bench-jsc-multi-eval PRIVATE
|
||||
-fconstexpr-steps=2542484
|
||||
-fconstexpr-depth=54
|
||||
-fno-pic
|
||||
-fno-pie
|
||||
-faddrsig
|
||||
)
|
||||
endif()
|
||||
target_link_libraries(bench-jsc-multi-eval PRIVATE
|
||||
${COLD_JSC_WEBKIT_PATH}/lib/libJavaScriptCore.a
|
||||
${COLD_JSC_WEBKIT_PATH}/lib/libWTF.a
|
||||
)
|
||||
if(EXISTS ${COLD_JSC_WEBKIT_PATH}/lib/libbmalloc.a)
|
||||
target_link_libraries(bench-jsc-multi-eval PRIVATE ${COLD_JSC_WEBKIT_PATH}/lib/libbmalloc.a)
|
||||
endif()
|
||||
if(APPLE)
|
||||
target_link_libraries(bench-jsc-multi-eval PRIVATE icucore resolv)
|
||||
target_compile_definitions(bench-jsc-multi-eval PRIVATE
|
||||
U_DISABLE_RENAMING=1
|
||||
_DARWIN_NON_CANCELABLE=1
|
||||
)
|
||||
target_link_options(bench-jsc-multi-eval PRIVATE
|
||||
-Wl,-ld_new
|
||||
-Wl,-no_compact_unwind
|
||||
-Wl,-w
|
||||
-fno-keep-static-consts
|
||||
)
|
||||
endif()
|
||||
if(LINUX)
|
||||
target_link_libraries(bench-jsc-multi-eval PRIVATE
|
||||
${COLD_JSC_WEBKIT_PATH}/lib/libicui18n.a
|
||||
${COLD_JSC_WEBKIT_PATH}/lib/libicuuc.a
|
||||
${COLD_JSC_WEBKIT_PATH}/lib/libicudata.a
|
||||
pthread dl atomic
|
||||
)
|
||||
target_link_options(bench-jsc-multi-eval PRIVATE -no-pie)
|
||||
endif()
|
||||
|
||||
else()
|
||||
message(STATUS "cold-jsc-start target disabled: WebKit not found")
|
||||
endif()
|
||||
endif()
|
||||
|
||||
# V8 benchmark tools for comparison with JSC
|
||||
#
|
||||
# Build with: cmake --build build/release --target cold-v8-start bench-v8-e2e bench-v8-100-e2e bench-v8-multi-eval
|
||||
#
|
||||
# V8 is searched in this order:
|
||||
# 1. V8_PATH environment variable or CMake variable (for custom/static builds)
|
||||
# 2. macOS: Homebrew /opt/homebrew/opt/v8
|
||||
# 3. Linux: pkg-config or /usr/local, /usr
|
||||
#
|
||||
# For static linking, build V8 from source:
|
||||
# cd vendor/node/deps/v8
|
||||
# tools/dev/gm.py x64.release
|
||||
# Then set V8_PATH to the output directory
|
||||
|
||||
set(V8_FOUND FALSE)
|
||||
set(V8_STATIC FALSE)
|
||||
|
||||
# Check for custom V8 path first (allows static builds)
|
||||
if(DEFINED ENV{V8_PATH})
|
||||
set(V8_PATH "$ENV{V8_PATH}")
|
||||
elseif(NOT DEFINED V8_PATH)
|
||||
set(V8_PATH "")
|
||||
endif()
|
||||
|
||||
# Try custom path first
|
||||
if(V8_PATH AND EXISTS ${V8_PATH}/include/v8.h)
|
||||
set(V8_FOUND TRUE)
|
||||
set(V8_INCLUDE_DIRS ${V8_PATH}/include)
|
||||
|
||||
# Prefer static libraries
|
||||
if(EXISTS ${V8_PATH}/lib/libv8_monolith.a)
|
||||
# Monolithic static build (recommended for embedding)
|
||||
set(V8_STATIC TRUE)
|
||||
set(V8_LIBRARIES ${V8_PATH}/lib/libv8_monolith.a)
|
||||
message(STATUS "V8 benchmarks will use static V8 monolith from: ${V8_PATH}")
|
||||
elseif(EXISTS ${V8_PATH}/lib/libv8.a)
|
||||
set(V8_STATIC TRUE)
|
||||
set(V8_LIBRARIES
|
||||
${V8_PATH}/lib/libv8.a
|
||||
${V8_PATH}/lib/libv8_libplatform.a
|
||||
${V8_PATH}/lib/libv8_libbase.a
|
||||
)
|
||||
message(STATUS "V8 benchmarks will use static V8 from: ${V8_PATH}")
|
||||
else()
|
||||
set(V8_LIBRARY_DIRS ${V8_PATH}/lib)
|
||||
set(V8_LIBRARIES v8 v8_libplatform v8_libbase)
|
||||
set(V8_RPATH "${V8_PATH}/lib")
|
||||
message(STATUS "V8 benchmarks will use V8 from: ${V8_PATH}")
|
||||
endif()
|
||||
set(V8_DEFINITIONS V8_COMPRESS_POINTERS V8_31BIT_SMIS_ON_64BIT_ARCH V8_ENABLE_SANDBOX)
|
||||
endif()
|
||||
|
||||
# macOS: Try Homebrew
|
||||
if(NOT V8_FOUND AND APPLE)
|
||||
set(V8_HOMEBREW_PATH "/opt/homebrew/opt/v8")
|
||||
if(EXISTS ${V8_HOMEBREW_PATH}/include/v8.h)
|
||||
set(V8_FOUND TRUE)
|
||||
set(V8_INCLUDE_DIRS ${V8_HOMEBREW_PATH}/include)
|
||||
set(V8_LIBRARY_DIRS ${V8_HOMEBREW_PATH}/lib)
|
||||
set(V8_LIBRARIES v8 v8_libplatform v8_libbase)
|
||||
set(V8_RPATH "${V8_HOMEBREW_PATH}/libexec")
|
||||
set(V8_DEFINITIONS V8_COMPRESS_POINTERS V8_31BIT_SMIS_ON_64BIT_ARCH V8_ENABLE_SANDBOX)
|
||||
message(STATUS "V8 benchmarks will use Homebrew V8 from: ${V8_HOMEBREW_PATH} (dynamic)")
|
||||
endif()
|
||||
endif()
|
||||
|
||||
# Linux: Try pkg-config or standard paths
|
||||
if(NOT V8_FOUND AND LINUX)
|
||||
find_package(PkgConfig QUIET)
|
||||
if(PkgConfig_FOUND)
|
||||
pkg_check_modules(V8_PKG QUIET v8 v8_libplatform)
|
||||
if(V8_PKG_FOUND)
|
||||
set(V8_FOUND TRUE)
|
||||
set(V8_INCLUDE_DIRS ${V8_PKG_INCLUDE_DIRS})
|
||||
set(V8_LIBRARY_DIRS ${V8_PKG_LIBRARY_DIRS})
|
||||
set(V8_LIBRARIES ${V8_PKG_LIBRARIES})
|
||||
set(V8_DEFINITIONS V8_COMPRESS_POINTERS V8_31BIT_SMIS_ON_64BIT_ARCH)
|
||||
message(STATUS "V8 benchmarks will use system V8 via pkg-config (dynamic)")
|
||||
endif()
|
||||
endif()
|
||||
|
||||
if(NOT V8_FOUND)
|
||||
foreach(V8_SEARCH_PATH /usr/local /usr)
|
||||
if(EXISTS ${V8_SEARCH_PATH}/include/v8.h OR EXISTS ${V8_SEARCH_PATH}/include/v8/v8.h)
|
||||
set(V8_FOUND TRUE)
|
||||
if(EXISTS ${V8_SEARCH_PATH}/include/v8/v8.h)
|
||||
set(V8_INCLUDE_DIRS ${V8_SEARCH_PATH}/include/v8)
|
||||
else()
|
||||
set(V8_INCLUDE_DIRS ${V8_SEARCH_PATH}/include)
|
||||
endif()
|
||||
# Prefer static
|
||||
if(EXISTS ${V8_SEARCH_PATH}/lib/libv8_monolith.a)
|
||||
set(V8_STATIC TRUE)
|
||||
set(V8_LIBRARIES ${V8_SEARCH_PATH}/lib/libv8_monolith.a)
|
||||
elseif(EXISTS ${V8_SEARCH_PATH}/lib/libv8.a)
|
||||
set(V8_STATIC TRUE)
|
||||
set(V8_LIBRARIES
|
||||
${V8_SEARCH_PATH}/lib/libv8.a
|
||||
${V8_SEARCH_PATH}/lib/libv8_libplatform.a
|
||||
)
|
||||
else()
|
||||
set(V8_LIBRARY_DIRS ${V8_SEARCH_PATH}/lib)
|
||||
set(V8_LIBRARIES v8 v8_libplatform)
|
||||
endif()
|
||||
set(V8_DEFINITIONS V8_COMPRESS_POINTERS V8_31BIT_SMIS_ON_64BIT_ARCH V8_ENABLE_SANDBOX)
|
||||
if(V8_STATIC)
|
||||
message(STATUS "V8 benchmarks will use static V8 from: ${V8_SEARCH_PATH}")
|
||||
else()
|
||||
message(STATUS "V8 benchmarks will use V8 from: ${V8_SEARCH_PATH} (dynamic)")
|
||||
endif()
|
||||
break()
|
||||
endif()
|
||||
endforeach()
|
||||
endif()
|
||||
endif()
|
||||
|
||||
if(V8_FOUND)
|
||||
# Helper function to set up V8 benchmark targets
|
||||
function(add_v8_benchmark TARGET_NAME SOURCE_FILE)
|
||||
add_executable(${TARGET_NAME} EXCLUDE_FROM_ALL ${SOURCE_FILE})
|
||||
set_target_properties(${TARGET_NAME} PROPERTIES
|
||||
CXX_STANDARD 20
|
||||
CXX_STANDARD_REQUIRED YES
|
||||
)
|
||||
target_include_directories(${TARGET_NAME} PRIVATE ${V8_INCLUDE_DIRS})
|
||||
target_compile_definitions(${TARGET_NAME} PRIVATE ${V8_DEFINITIONS})
|
||||
if(V8_LIBRARY_DIRS)
|
||||
target_link_directories(${TARGET_NAME} PRIVATE ${V8_LIBRARY_DIRS})
|
||||
endif()
|
||||
target_link_libraries(${TARGET_NAME} PRIVATE ${V8_LIBRARIES})
|
||||
if(V8_STATIC)
|
||||
# Static V8 needs these system libraries
|
||||
if(APPLE)
|
||||
target_link_libraries(${TARGET_NAME} PRIVATE pthread dl)
|
||||
elseif(LINUX)
|
||||
target_link_libraries(${TARGET_NAME} PRIVATE pthread dl rt atomic)
|
||||
# Use lld linker to handle V8's chromium-built objects
|
||||
target_link_options(${TARGET_NAME} PRIVATE -fuse-ld=lld)
|
||||
endif()
|
||||
elseif(V8_RPATH)
|
||||
set_target_properties(${TARGET_NAME} PROPERTIES
|
||||
INSTALL_RPATH "${V8_RPATH}"
|
||||
BUILD_WITH_INSTALL_RPATH TRUE
|
||||
)
|
||||
endif()
|
||||
endfunction()
|
||||
|
||||
add_v8_benchmark(cold-v8-start ${CWD}/misctools/cold-v8-start.cpp)
|
||||
add_v8_benchmark(bench-v8-e2e ${CWD}/misctools/bench-v8-e2e.cpp)
|
||||
add_v8_benchmark(bench-v8-100-e2e ${CWD}/misctools/bench-v8-100-e2e.cpp)
|
||||
add_v8_benchmark(bench-v8-multi-eval ${CWD}/misctools/bench-v8-multi-eval.cpp)
|
||||
|
||||
else()
|
||||
message(STATUS "V8 benchmarks disabled: V8 not found")
|
||||
message(STATUS " To enable: set V8_PATH to a V8 build directory, or:")
|
||||
message(STATUS " - macOS: brew install v8")
|
||||
message(STATUS " - Linux: apt install libv8-dev, or build from source")
|
||||
endif()
|
||||
124
misctools/bench-compare-cold-start.sh
Executable file
124
misctools/bench-compare-cold-start.sh
Executable file
@@ -0,0 +1,124 @@
|
||||
#!/bin/bash
|
||||
# Compare JSC vs V8 Cold Start Performance
|
||||
# Usage: ./bench-compare-cold-start.sh
|
||||
|
||||
set -e
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
|
||||
BUN_DIR="$(dirname "$SCRIPT_DIR")"
|
||||
COLD_JSC="$BUN_DIR/build/release/cold-jsc-start"
|
||||
COLD_V8="$BUN_DIR/build/release/cold-v8-start"
|
||||
|
||||
# Build if needed
|
||||
if [[ ! -x "$COLD_JSC" ]] || [[ ! -x "$COLD_V8" ]]; then
|
||||
echo "Building benchmark tools..."
|
||||
cmake --build "$BUN_DIR/build/release" --target cold-jsc-start cold-v8-start >/dev/null 2>&1
|
||||
fi
|
||||
|
||||
echo "╔══════════════════════════════════════════════════════════════╗"
|
||||
echo "║ JSC vs V8 Cold Start Benchmark Comparison ║"
|
||||
echo "╚══════════════════════════════════════════════════════════════╝"
|
||||
echo ""
|
||||
|
||||
# Single cold start comparison
|
||||
echo "┌────────────────────────────────────────────────────────────────┐"
|
||||
echo "│ Single Cold Start (average of 5 runs, fresh process each) │"
|
||||
echo "└────────────────────────────────────────────────────────────────┘"
|
||||
echo ""
|
||||
|
||||
echo "JavaScriptCore:"
|
||||
jsc_total=0
|
||||
for i in $(seq 1 5); do
|
||||
output=$("$COLD_JSC" -e "write('')" 2>&1)
|
||||
init=$(echo "$output" | grep "Initialize" | awk '{print $3}')
|
||||
vm=$(echo "$output" | grep "VM::create" | awk '{print $3}')
|
||||
global=$(echo "$output" | grep "GlobalObject" | awk '{print $3}')
|
||||
total=$(echo "$init + $vm + $global" | bc)
|
||||
jsc_total=$(echo "$jsc_total + $total" | bc)
|
||||
printf " Run %d: Init=%.2fms VM=%.2fms GlobalObject=%.2fms Total=%.2fms\n" $i $init $vm $global $total
|
||||
done
|
||||
jsc_avg=$(echo "scale=3; $jsc_total / 5" | bc)
|
||||
echo ""
|
||||
|
||||
echo "V8:"
|
||||
v8_total=0
|
||||
for i in $(seq 1 5); do
|
||||
output=$("$COLD_V8" -e "write('')" 2>&1)
|
||||
init=$(echo "$output" | grep "Initialize" | awk '{print $3}')
|
||||
isolate=$(echo "$output" | grep "Isolate::New" | awk '{print $3}')
|
||||
context=$(echo "$output" | grep "Context::New" | awk '{print $3}')
|
||||
total=$(echo "$init + $isolate + $context" | bc)
|
||||
v8_total=$(echo "$v8_total + $total" | bc)
|
||||
printf " Run %d: Init=%.2fms Isolate=%.2fms Context=%.2fms Total=%.2fms\n" $i $init $isolate $context $total
|
||||
done
|
||||
v8_avg=$(echo "scale=3; $v8_total / 5" | bc)
|
||||
echo ""
|
||||
|
||||
speedup=$(echo "scale=2; $v8_avg / $jsc_avg" | bc)
|
||||
printf "Average: JSC=%.2fms V8=%.2fms (JSC is %.1fx faster)\n" $jsc_avg $v8_avg $speedup
|
||||
echo ""
|
||||
|
||||
# 100 VMs/Isolates benchmark
|
||||
echo "┌────────────────────────────────────────────────────────────────┐"
|
||||
echo "│ 100 VMs/Isolates + GlobalObjects/Contexts (best of 3 runs) │"
|
||||
echo "└────────────────────────────────────────────────────────────────┘"
|
||||
echo ""
|
||||
|
||||
echo "JavaScriptCore (100 VMs + GlobalObjects):"
|
||||
jsc_best=999999
|
||||
for i in $(seq 1 3); do
|
||||
output=$("$COLD_JSC" --benchmark-vm 2>&1)
|
||||
# Format: "Created 100 VMs + GlobalObjects in 22.015750 ms (0.220158 ms per VM+GlobalObject)"
|
||||
time=$(echo "$output" | grep "GlobalObjects" | sed 's/.*(\([0-9.]*\) ms.*/\1/')
|
||||
if (( $(echo "$time < $jsc_best" | bc -l) )); then
|
||||
jsc_best=$time
|
||||
fi
|
||||
echo " Run $i: $time ms per VM+GlobalObject"
|
||||
done
|
||||
echo ""
|
||||
|
||||
echo "V8 (100 Isolates + Contexts):"
|
||||
v8_best=999999
|
||||
for i in $(seq 1 3); do
|
||||
output=$("$COLD_V8" --benchmark-isolate 2>&1)
|
||||
# Format: "Created 100 Isolates + Contexts in 24.277375 ms (0.242774 ms per Isolate+Context)"
|
||||
time=$(echo "$output" | grep "Isolate+Context" | sed 's/.*(\([0-9.]*\) ms.*/\1/')
|
||||
if (( $(echo "$time < $v8_best" | bc -l) )); then
|
||||
v8_best=$time
|
||||
fi
|
||||
echo " Run $i: $time ms per Isolate+Context"
|
||||
done
|
||||
echo ""
|
||||
|
||||
speedup=$(echo "scale=2; $v8_best / $jsc_best" | bc)
|
||||
printf "Best: JSC=%.3fms/vm V8=%.3fms/isolate (JSC is %.1fx faster)\n" $jsc_best $v8_best $speedup
|
||||
echo ""
|
||||
|
||||
# Memory comparison
|
||||
echo "┌────────────────────────────────────────────────────────────────┐"
|
||||
echo "│ Memory Usage (100 VMs/Isolates + GlobalObjects/Contexts) │"
|
||||
echo "└────────────────────────────────────────────────────────────────┘"
|
||||
echo ""
|
||||
|
||||
echo "JavaScriptCore:"
|
||||
jsc_mem=$(/usr/bin/time -l "$COLD_JSC" --benchmark-vm 2>&1 | grep "maximum resident" | awk '{print $1}')
|
||||
jsc_mem_mb=$(echo "scale=1; $jsc_mem / 1048576" | bc)
|
||||
echo " Maximum RSS: ${jsc_mem_mb} MB"
|
||||
|
||||
echo ""
|
||||
echo "V8:"
|
||||
v8_mem=$(/usr/bin/time -l "$COLD_V8" --benchmark-isolate 2>&1 | grep "maximum resident" | awk '{print $1}')
|
||||
v8_mem_mb=$(echo "scale=1; $v8_mem / 1048576" | bc)
|
||||
echo " Maximum RSS: ${v8_mem_mb} MB"
|
||||
|
||||
echo ""
|
||||
mem_ratio=$(echo "scale=2; $v8_mem / $jsc_mem" | bc)
|
||||
printf "Memory: JSC=%.1fMB V8=%.1fMB (V8 uses %.1fx more memory)\n" $jsc_mem_mb $v8_mem_mb $mem_ratio
|
||||
echo ""
|
||||
|
||||
echo "╔══════════════════════════════════════════════════════════════╗"
|
||||
echo "║ Summary ║"
|
||||
echo "╚══════════════════════════════════════════════════════════════╝"
|
||||
printf " Single cold start: JSC is %.1fx faster\n" $(echo "scale=1; $v8_avg / $jsc_avg" | bc)
|
||||
printf " 100 VM/Isolate batch: JSC is %.1fx faster\n" $(echo "scale=1; $v8_best / $jsc_best" | bc)
|
||||
printf " Memory per instance: V8 uses %.1fx more memory\n" $mem_ratio
|
||||
122
misctools/bench-jsc-100-e2e.cpp
Normal file
122
misctools/bench-jsc-100-e2e.cpp
Normal file
@@ -0,0 +1,122 @@
|
||||
// JSC 100 full E2E benchmark - 100 VMs + GlobalObjects + eval
|
||||
//
|
||||
// Build: cmake --build build/release --target bench-jsc-100-e2e
|
||||
// Usage: ./bench-jsc-100-e2e
|
||||
|
||||
#define ENABLE_COCOA_WEBM_PLAYER 0
|
||||
#include "root.h"
|
||||
|
||||
#include <sys/resource.h>
|
||||
|
||||
#include <JavaScriptCore/JSGlobalObject.h>
|
||||
#include <JavaScriptCore/Completion.h>
|
||||
#include <JavaScriptCore/InitializeThreading.h>
|
||||
#include <JavaScriptCore/JSCConfig.h>
|
||||
#include <wtf/Stopwatch.h>
|
||||
|
||||
using namespace JSC;
|
||||
|
||||
class MinimalClientData : public JSC::VM::ClientData {
|
||||
public:
|
||||
MinimalClientData() = default;
|
||||
virtual ~MinimalClientData() = default;
|
||||
WTF::String overrideSourceURL(const JSC::StackFrame&, const WTF::String& originalSourceURL) const override {
|
||||
return originalSourceURL;
|
||||
}
|
||||
};
|
||||
|
||||
extern "C" {
|
||||
void Bun__errorInstance__finalize(void*) {}
|
||||
static char dummyTimer = 0;
|
||||
void* WTFTimer__create(void*, void*, void*) { return &dummyTimer; }
|
||||
void WTFTimer__deinit(void*) {}
|
||||
void WTFTimer__cancel(void*) {}
|
||||
void WTFTimer__update(void*, double, bool) {}
|
||||
bool WTFTimer__isActive(void*) { return false; }
|
||||
double WTFTimer__secondsUntilTimer(void*) { return 0.0; }
|
||||
void* Bun__getVM() { return nullptr; }
|
||||
}
|
||||
|
||||
int main() {
|
||||
constexpr int NUM_VMS = 100;
|
||||
|
||||
// Initialize JSC (one-time cost)
|
||||
WTF::initialize();
|
||||
JSC::Config::enableRestrictedOptions();
|
||||
WTF::initializeMainThread();
|
||||
JSC::initialize();
|
||||
{
|
||||
JSC::Options::AllowUnfinalizedAccessScope scope;
|
||||
JSC::Options::useConcurrentJIT() = true;
|
||||
JSC::Options::useJIT() = true;
|
||||
JSC::Options::assertOptionsAreCoherent();
|
||||
}
|
||||
|
||||
fprintf(stderr, "JSC 100 Full E2E Benchmark:\n\n");
|
||||
|
||||
// Keep VMs alive
|
||||
JSC::VM* vms[NUM_VMS];
|
||||
JSC::JSGlobalObject* globalObjects[NUM_VMS];
|
||||
|
||||
auto timer = Stopwatch::create();
|
||||
timer->start();
|
||||
|
||||
for (int i = 0; i < NUM_VMS; i++) {
|
||||
// Create VM
|
||||
auto vmPtr = JSC::VM::tryCreate(JSC::HeapType::Large);
|
||||
if (!vmPtr) {
|
||||
fprintf(stderr, "Failed to create VM %d\n", i);
|
||||
return 1;
|
||||
}
|
||||
vmPtr->refSuppressingSaferCPPChecking();
|
||||
vms[i] = vmPtr.get();
|
||||
vms[i]->heap.acquireAccess();
|
||||
|
||||
JSC::JSLockHolder locker(*vms[i]);
|
||||
vms[i]->clientData = new MinimalClientData();
|
||||
|
||||
// Create GlobalObject
|
||||
auto* structure = JSC::JSGlobalObject::createStructure(*vms[i], JSC::jsNull());
|
||||
globalObjects[i] = JSC::JSGlobalObject::create(*vms[i], structure);
|
||||
JSC::gcProtect(globalObjects[i]);
|
||||
|
||||
// Eval script (slightly different each time)
|
||||
char scriptBuf[128];
|
||||
snprintf(scriptBuf, sizeof(scriptBuf),
|
||||
"var x = %d; for (var j = 0; j < 100; j++) x += j; x", i);
|
||||
|
||||
auto source = JSC::makeSource(
|
||||
WTF::String::fromUTF8(scriptBuf),
|
||||
SourceOrigin(WTF::URL("file://script.js"_s)),
|
||||
JSC::SourceTaintedOrigin::Untainted, "script.js"_s);
|
||||
|
||||
NakedPtr<Exception> exception;
|
||||
JSValue result = JSC::profiledEvaluate(globalObjects[i], ProfilingReason::API,
|
||||
source, globalObjects[i], exception);
|
||||
|
||||
if (exception) {
|
||||
fprintf(stderr, "Exception in VM %d: %s\n", i,
|
||||
exception->value().toWTFString(globalObjects[i]).utf8().data());
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
double totalTime = timer->elapsedTime().milliseconds();
|
||||
|
||||
// Get memory usage
|
||||
struct rusage usage;
|
||||
getrusage(RUSAGE_SELF, &usage);
|
||||
#ifdef __APPLE__
|
||||
double rssBytes = usage.ru_maxrss; // bytes on macOS
|
||||
#else
|
||||
double rssBytes = usage.ru_maxrss * 1024; // kilobytes on Linux
|
||||
#endif
|
||||
double rssMB = rssBytes / (1024 * 1024);
|
||||
|
||||
fprintf(stderr, " Created %d VMs + GlobalObjects + eval\n", NUM_VMS);
|
||||
fprintf(stderr, " Total time: %6.3f ms\n", totalTime);
|
||||
fprintf(stderr, " Per instance: %6.3f ms\n", totalTime / NUM_VMS);
|
||||
fprintf(stderr, " Peak RSS: %6.1f MB (%.2f MB per instance)\n", rssMB, rssMB / NUM_VMS);
|
||||
|
||||
return 0;
|
||||
}
|
||||
38
misctools/bench-jsc-cold-start.sh
Executable file
38
misctools/bench-jsc-cold-start.sh
Executable file
@@ -0,0 +1,38 @@
|
||||
#!/bin/bash
|
||||
# JSC Cold Start Benchmark
|
||||
# Usage: ./bench-jsc-cold-start.sh [iterations]
|
||||
|
||||
set -e
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
|
||||
BUN_DIR="$(dirname "$SCRIPT_DIR")"
|
||||
COLD_JSC="$BUN_DIR/build/release/cold-jsc-start"
|
||||
|
||||
if [[ ! -x "$COLD_JSC" ]]; then
|
||||
echo "Building cold-jsc-start..."
|
||||
cmake --build "$BUN_DIR/build/release" --target cold-jsc-start >/dev/null 2>&1
|
||||
fi
|
||||
|
||||
ITERATIONS=${1:-5}
|
||||
|
||||
echo "=== JavaScriptCore Cold Start Benchmark ==="
|
||||
echo ""
|
||||
|
||||
# Single cold start
|
||||
echo "--- Single Cold Start (fresh process) ---"
|
||||
for i in $(seq 1 $ITERATIONS); do
|
||||
"$COLD_JSC" -e "write('')" 2>&1 | grep -v "^$"
|
||||
echo ""
|
||||
done
|
||||
|
||||
# 100 VMs benchmark
|
||||
echo "--- 100 VMs Benchmark ---"
|
||||
for i in $(seq 1 3); do
|
||||
echo "Run $i:"
|
||||
"$COLD_JSC" --benchmark-vm 2>&1
|
||||
echo ""
|
||||
done
|
||||
|
||||
# Memory usage
|
||||
echo "--- Memory Usage (100 VMs + GlobalObjects) ---"
|
||||
/usr/bin/time -l "$COLD_JSC" --benchmark-vm 2>&1 | grep -E "(VMs|GlobalObjects|maximum resident|peak memory)"
|
||||
112
misctools/bench-jsc-e2e.cpp
Normal file
112
misctools/bench-jsc-e2e.cpp
Normal file
@@ -0,0 +1,112 @@
|
||||
// JSC single end-to-end cold start benchmark
|
||||
//
|
||||
// Measures: process start → JSC init → VM → GlobalObject → eval → exit
|
||||
//
|
||||
// Build: cmake --build build/release --target bench-jsc-e2e
|
||||
// Usage: ./bench-jsc-e2e
|
||||
|
||||
#define ENABLE_COCOA_WEBM_PLAYER 0
|
||||
#include "root.h"
|
||||
|
||||
#include <sys/resource.h>
|
||||
|
||||
#include <JavaScriptCore/JSGlobalObject.h>
|
||||
#include <JavaScriptCore/Completion.h>
|
||||
#include <JavaScriptCore/InitializeThreading.h>
|
||||
#include <JavaScriptCore/JSCConfig.h>
|
||||
#include <wtf/Stopwatch.h>
|
||||
|
||||
using namespace JSC;
|
||||
|
||||
class MinimalClientData : public JSC::VM::ClientData {
|
||||
public:
|
||||
MinimalClientData() = default;
|
||||
virtual ~MinimalClientData() = default;
|
||||
WTF::String overrideSourceURL(const JSC::StackFrame&, const WTF::String& originalSourceURL) const override {
|
||||
return originalSourceURL;
|
||||
}
|
||||
};
|
||||
|
||||
extern "C" {
|
||||
void Bun__errorInstance__finalize(void*) {}
|
||||
static char dummyTimer = 0;
|
||||
void* WTFTimer__create(void*, void*, void*) { return &dummyTimer; }
|
||||
void WTFTimer__deinit(void*) {}
|
||||
void WTFTimer__cancel(void*) {}
|
||||
void WTFTimer__update(void*, double, bool) {}
|
||||
bool WTFTimer__isActive(void*) { return false; }
|
||||
double WTFTimer__secondsUntilTimer(void*) { return 0.0; }
|
||||
void* Bun__getVM() { return nullptr; }
|
||||
}
|
||||
|
||||
int main() {
|
||||
auto totalTimer = Stopwatch::create();
|
||||
totalTimer->start();
|
||||
|
||||
// Initialize
|
||||
WTF::initialize();
|
||||
JSC::Config::enableRestrictedOptions();
|
||||
WTF::initializeMainThread();
|
||||
JSC::initialize();
|
||||
{
|
||||
JSC::Options::AllowUnfinalizedAccessScope scope;
|
||||
JSC::Options::useConcurrentJIT() = true;
|
||||
JSC::Options::useJIT() = true;
|
||||
JSC::Options::assertOptionsAreCoherent();
|
||||
}
|
||||
|
||||
double initTime = totalTimer->elapsedTime().milliseconds();
|
||||
|
||||
// Create VM
|
||||
auto vmPtr = JSC::VM::tryCreate(JSC::HeapType::Large);
|
||||
vmPtr->refSuppressingSaferCPPChecking();
|
||||
auto& vm = *vmPtr;
|
||||
vm.heap.acquireAccess();
|
||||
JSC::JSLockHolder locker(vm);
|
||||
vm.clientData = new MinimalClientData();
|
||||
|
||||
double vmTime = totalTimer->elapsedTime().milliseconds();
|
||||
|
||||
// Create GlobalObject
|
||||
auto* structure = JSC::JSGlobalObject::createStructure(vm, JSC::jsNull());
|
||||
auto* globalObject = JSC::JSGlobalObject::create(vm, structure);
|
||||
JSC::gcProtect(globalObject);
|
||||
|
||||
double globalObjectTime = totalTimer->elapsedTime().milliseconds();
|
||||
|
||||
// Eval simple script
|
||||
auto source = JSC::makeSource(
|
||||
"var x = 0; for (var i = 0; i < 1000; i++) x += i; x"_s,
|
||||
SourceOrigin(WTF::URL("file://bench.js"_s)),
|
||||
JSC::SourceTaintedOrigin::Untainted, "bench.js"_s);
|
||||
|
||||
NakedPtr<Exception> exception;
|
||||
JSValue result = JSC::profiledEvaluate(globalObject, ProfilingReason::API, source, globalObject, exception);
|
||||
|
||||
double evalTime = totalTimer->elapsedTime().milliseconds();
|
||||
|
||||
// Get memory usage
|
||||
struct rusage usage;
|
||||
getrusage(RUSAGE_SELF, &usage);
|
||||
#ifdef __APPLE__
|
||||
double rssBytes = usage.ru_maxrss; // bytes on macOS
|
||||
#else
|
||||
double rssBytes = usage.ru_maxrss * 1024; // kilobytes on Linux
|
||||
#endif
|
||||
double rssMB = rssBytes / (1024 * 1024);
|
||||
|
||||
fprintf(stderr, "JSC E2E Cold Start:\n");
|
||||
fprintf(stderr, " Initialize: %6.3f ms\n", initTime);
|
||||
fprintf(stderr, " VM: %6.3f ms (+%.3f)\n", vmTime, vmTime - initTime);
|
||||
fprintf(stderr, " GlobalObject: %6.3f ms (+%.3f)\n", globalObjectTime, globalObjectTime - vmTime);
|
||||
fprintf(stderr, " Eval: %6.3f ms (+%.3f)\n", evalTime, evalTime - globalObjectTime);
|
||||
fprintf(stderr, " Total: %6.3f ms\n", evalTime);
|
||||
fprintf(stderr, " Peak RSS: %6.1f MB\n", rssMB);
|
||||
|
||||
if (exception) {
|
||||
fprintf(stderr, "Exception: %s\n", exception->value().toWTFString(globalObject).utf8().data());
|
||||
return 1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
149
misctools/bench-jsc-multi-eval.cpp
Normal file
149
misctools/bench-jsc-multi-eval.cpp
Normal file
@@ -0,0 +1,149 @@
|
||||
// JSC multi-eval benchmark - 1000 scripts in same VM
|
||||
//
|
||||
// Tests compile + eval performance for same vs varied scripts
|
||||
//
|
||||
// Build: cmake --build build/release --target bench-jsc-multi-eval
|
||||
// Usage: ./bench-jsc-multi-eval
|
||||
|
||||
#define ENABLE_COCOA_WEBM_PLAYER 0
|
||||
#include "root.h"
|
||||
|
||||
#include <sys/resource.h>
|
||||
|
||||
#include <JavaScriptCore/JSGlobalObject.h>
|
||||
#include <JavaScriptCore/Completion.h>
|
||||
#include <JavaScriptCore/InitializeThreading.h>
|
||||
#include <JavaScriptCore/JSCConfig.h>
|
||||
#include <wtf/Stopwatch.h>
|
||||
|
||||
using namespace JSC;
|
||||
|
||||
class MinimalClientData : public JSC::VM::ClientData {
|
||||
public:
|
||||
MinimalClientData() = default;
|
||||
virtual ~MinimalClientData() = default;
|
||||
WTF::String overrideSourceURL(const JSC::StackFrame&, const WTF::String& originalSourceURL) const override {
|
||||
return originalSourceURL;
|
||||
}
|
||||
};
|
||||
|
||||
extern "C" {
|
||||
void Bun__errorInstance__finalize(void*) {}
|
||||
static char dummyTimer = 0;
|
||||
void* WTFTimer__create(void*, void*, void*) { return &dummyTimer; }
|
||||
void WTFTimer__deinit(void*) {}
|
||||
void WTFTimer__cancel(void*) {}
|
||||
void WTFTimer__update(void*, double, bool) {}
|
||||
bool WTFTimer__isActive(void*) { return false; }
|
||||
double WTFTimer__secondsUntilTimer(void*) { return 0.0; }
|
||||
void* Bun__getVM() { return nullptr; }
|
||||
}
|
||||
|
||||
static double getRSSMB() {
|
||||
struct rusage usage;
|
||||
getrusage(RUSAGE_SELF, &usage);
|
||||
#ifdef __APPLE__
|
||||
return usage.ru_maxrss / (1024.0 * 1024.0);
|
||||
#else
|
||||
return usage.ru_maxrss / 1024.0;
|
||||
#endif
|
||||
}
|
||||
|
||||
int main() {
|
||||
constexpr int NUM_SCRIPTS = 1000;
|
||||
|
||||
// Initialize
|
||||
WTF::initialize();
|
||||
JSC::Config::enableRestrictedOptions();
|
||||
WTF::initializeMainThread();
|
||||
JSC::initialize();
|
||||
{
|
||||
JSC::Options::AllowUnfinalizedAccessScope scope;
|
||||
JSC::Options::useConcurrentJIT() = true;
|
||||
JSC::Options::useJIT() = true;
|
||||
JSC::Options::assertOptionsAreCoherent();
|
||||
}
|
||||
|
||||
// Create VM + GlobalObject
|
||||
auto vmPtr = JSC::VM::tryCreate(JSC::HeapType::Large);
|
||||
vmPtr->refSuppressingSaferCPPChecking();
|
||||
auto& vm = *vmPtr;
|
||||
vm.heap.acquireAccess();
|
||||
JSC::JSLockHolder locker(vm);
|
||||
vm.clientData = new MinimalClientData();
|
||||
|
||||
auto* structure = JSC::JSGlobalObject::createStructure(vm, JSC::jsNull());
|
||||
auto* globalObject = JSC::JSGlobalObject::create(vm, structure);
|
||||
JSC::gcProtect(globalObject);
|
||||
|
||||
// ============ SAME SCRIPT (1000x) ============
|
||||
{
|
||||
auto timer = Stopwatch::create();
|
||||
timer->start();
|
||||
|
||||
const char* sameScript = "function compute(n) { var sum = 0; for (var j = 0; j < n; j++) sum += j; return sum; } compute(100)";
|
||||
|
||||
for (int i = 0; i < NUM_SCRIPTS; i++) {
|
||||
char nameBuf[32];
|
||||
snprintf(nameBuf, sizeof(nameBuf), "same_%d.js", i);
|
||||
|
||||
auto source = JSC::makeSource(
|
||||
WTF::String::fromUTF8(sameScript),
|
||||
SourceOrigin(WTF::URL(WTF::String::fromUTF8(nameBuf))),
|
||||
JSC::SourceTaintedOrigin::Untainted,
|
||||
WTF::String::fromUTF8(nameBuf));
|
||||
|
||||
NakedPtr<Exception> exception;
|
||||
JSValue result = JSC::profiledEvaluate(globalObject, ProfilingReason::API, source, globalObject, exception);
|
||||
|
||||
if (exception) {
|
||||
fprintf(stderr, "Exception in same script %d: %s\n", i,
|
||||
exception->value().toWTFString(globalObject).utf8().data());
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
double totalTime = timer->elapsedTime().milliseconds();
|
||||
double rssMB = getRSSMB();
|
||||
|
||||
fprintf(stderr, "same_script: %8.3f ms %6.1f MB\n", totalTime, rssMB);
|
||||
}
|
||||
|
||||
// ============ DIFFERENT SCRIPTS (1000x) ============
|
||||
{
|
||||
auto timer = Stopwatch::create();
|
||||
timer->start();
|
||||
|
||||
for (int i = 0; i < NUM_SCRIPTS; i++) {
|
||||
char scriptBuf[256];
|
||||
snprintf(scriptBuf, sizeof(scriptBuf),
|
||||
"function compute_%d(n) { var sum = %d; for (var j = 0; j < n; j++) sum += j * %d; return sum; } compute_%d(100)",
|
||||
i, i, i + 1, i);
|
||||
|
||||
char nameBuf[32];
|
||||
snprintf(nameBuf, sizeof(nameBuf), "diff_%d.js", i);
|
||||
|
||||
auto source = JSC::makeSource(
|
||||
WTF::String::fromUTF8(scriptBuf),
|
||||
SourceOrigin(WTF::URL(WTF::String::fromUTF8(nameBuf))),
|
||||
JSC::SourceTaintedOrigin::Untainted,
|
||||
WTF::String::fromUTF8(nameBuf));
|
||||
|
||||
NakedPtr<Exception> exception;
|
||||
JSValue result = JSC::profiledEvaluate(globalObject, ProfilingReason::API, source, globalObject, exception);
|
||||
|
||||
if (exception) {
|
||||
fprintf(stderr, "Exception in diff script %d: %s\n", i,
|
||||
exception->value().toWTFString(globalObject).utf8().data());
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
double totalTime = timer->elapsedTime().milliseconds();
|
||||
double rssMB = getRSSMB();
|
||||
|
||||
fprintf(stderr, "diff_script: %8.3f ms %6.1f MB\n", totalTime, rssMB);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
106
misctools/bench-v8-100-e2e.cpp
Normal file
106
misctools/bench-v8-100-e2e.cpp
Normal file
@@ -0,0 +1,106 @@
|
||||
// V8 100 full E2E benchmark - 100 Isolates + Contexts + eval
|
||||
//
|
||||
// Build: cmake --build build/release --target bench-v8-100-e2e
|
||||
// Usage: ./bench-v8-100-e2e
|
||||
|
||||
#include <sys/resource.h>
|
||||
#include <v8.h>
|
||||
#include <libplatform/libplatform.h>
|
||||
#include <chrono>
|
||||
#include <cstdio>
|
||||
|
||||
using namespace v8;
|
||||
|
||||
class Timer {
|
||||
public:
|
||||
Timer() { start(); }
|
||||
void start() { start_ = std::chrono::high_resolution_clock::now(); }
|
||||
double elapsedMs() const {
|
||||
auto now = std::chrono::high_resolution_clock::now();
|
||||
return std::chrono::duration<double, std::milli>(now - start_).count();
|
||||
}
|
||||
private:
|
||||
std::chrono::high_resolution_clock::time_point start_;
|
||||
};
|
||||
|
||||
int main(int argc, char* argv[]) {
|
||||
constexpr int NUM_ISOLATES = 100;
|
||||
|
||||
// Initialize V8 (one-time cost)
|
||||
V8::InitializeICUDefaultLocation(argv[0]);
|
||||
V8::InitializeExternalStartupData(argv[0]);
|
||||
std::unique_ptr<Platform> platform = platform::NewDefaultPlatform();
|
||||
V8::InitializePlatform(platform.get());
|
||||
V8::Initialize();
|
||||
|
||||
fprintf(stderr, "V8 100 Full E2E Benchmark:\n\n");
|
||||
|
||||
// Keep isolates alive
|
||||
Isolate* isolates[NUM_ISOLATES];
|
||||
Global<Context>* contexts[NUM_ISOLATES];
|
||||
|
||||
Isolate::CreateParams create_params;
|
||||
create_params.array_buffer_allocator = ArrayBuffer::Allocator::NewDefaultAllocator();
|
||||
|
||||
Timer timer;
|
||||
|
||||
for (int i = 0; i < NUM_ISOLATES; i++) {
|
||||
// Create Isolate
|
||||
isolates[i] = Isolate::New(create_params);
|
||||
if (!isolates[i]) {
|
||||
fprintf(stderr, "Failed to create isolate %d\n", i);
|
||||
return 1;
|
||||
}
|
||||
|
||||
Isolate::Scope isolate_scope(isolates[i]);
|
||||
HandleScope handle_scope(isolates[i]);
|
||||
|
||||
// Create Context
|
||||
Local<Context> context = Context::New(isolates[i]);
|
||||
contexts[i] = new Global<Context>(isolates[i], context);
|
||||
|
||||
Context::Scope context_scope(context);
|
||||
|
||||
// Eval script (slightly different each time)
|
||||
char scriptBuf[128];
|
||||
snprintf(scriptBuf, sizeof(scriptBuf),
|
||||
"var x = %d; for (var j = 0; j < 100; j++) x += j; x", i);
|
||||
|
||||
Local<String> source = String::NewFromUtf8(isolates[i], scriptBuf).ToLocalChecked();
|
||||
|
||||
TryCatch try_catch(isolates[i]);
|
||||
Local<Script> script;
|
||||
if (!Script::Compile(context, source).ToLocal(&script)) {
|
||||
String::Utf8Value error(isolates[i], try_catch.Exception());
|
||||
fprintf(stderr, "Compile error in isolate %d: %s\n", i, *error);
|
||||
return 1;
|
||||
}
|
||||
|
||||
Local<Value> result;
|
||||
if (!script->Run(context).ToLocal(&result)) {
|
||||
String::Utf8Value error(isolates[i], try_catch.Exception());
|
||||
fprintf(stderr, "Exception in isolate %d: %s\n", i, *error);
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
double totalTime = timer.elapsedMs();
|
||||
|
||||
// Get memory usage
|
||||
struct rusage usage;
|
||||
getrusage(RUSAGE_SELF, &usage);
|
||||
#ifdef __APPLE__
|
||||
double rssBytes = usage.ru_maxrss; // bytes on macOS
|
||||
#else
|
||||
double rssBytes = usage.ru_maxrss * 1024; // kilobytes on Linux
|
||||
#endif
|
||||
double rssMB = rssBytes / (1024 * 1024);
|
||||
|
||||
fprintf(stderr, " Created %d Isolates + Contexts + eval\n", NUM_ISOLATES);
|
||||
fprintf(stderr, " Total time: %6.3f ms\n", totalTime);
|
||||
fprintf(stderr, " Per instance: %6.3f ms\n", totalTime / NUM_ISOLATES);
|
||||
fprintf(stderr, " Peak RSS: %6.1f MB (%.2f MB per instance)\n", rssMB, rssMB / NUM_ISOLATES);
|
||||
|
||||
// Skip cleanup - just exit like JSC benchmarks do
|
||||
return 0;
|
||||
}
|
||||
38
misctools/bench-v8-cold-start.sh
Executable file
38
misctools/bench-v8-cold-start.sh
Executable file
@@ -0,0 +1,38 @@
|
||||
#!/bin/bash
|
||||
# V8 Cold Start Benchmark
|
||||
# Usage: ./bench-v8-cold-start.sh [iterations]
|
||||
|
||||
set -e
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
|
||||
BUN_DIR="$(dirname "$SCRIPT_DIR")"
|
||||
COLD_V8="$BUN_DIR/build/release/cold-v8-start"
|
||||
|
||||
if [[ ! -x "$COLD_V8" ]]; then
|
||||
echo "Building cold-v8-start..."
|
||||
cmake --build "$BUN_DIR/build/release" --target cold-v8-start >/dev/null 2>&1
|
||||
fi
|
||||
|
||||
ITERATIONS=${1:-5}
|
||||
|
||||
echo "=== V8 Isolate Cold Start Benchmark ==="
|
||||
echo ""
|
||||
|
||||
# Single cold start
|
||||
echo "--- Single Cold Start (fresh process) ---"
|
||||
for i in $(seq 1 $ITERATIONS); do
|
||||
"$COLD_V8" -e "write('')" 2>&1 | grep -v "^$"
|
||||
echo ""
|
||||
done
|
||||
|
||||
# 100 Isolates benchmark
|
||||
echo "--- 100 Isolates Benchmark ---"
|
||||
for i in $(seq 1 3); do
|
||||
echo "Run $i:"
|
||||
"$COLD_V8" --benchmark-isolate 2>&1
|
||||
echo ""
|
||||
done
|
||||
|
||||
# Memory usage
|
||||
echo "--- Memory Usage (100 Isolates + Contexts) ---"
|
||||
/usr/bin/time -l "$COLD_V8" --benchmark-isolate 2>&1 | grep -E "(Isolates|Contexts|maximum resident|peak memory)"
|
||||
100
misctools/bench-v8-e2e.cpp
Normal file
100
misctools/bench-v8-e2e.cpp
Normal file
@@ -0,0 +1,100 @@
|
||||
// V8 single end-to-end cold start benchmark
|
||||
//
|
||||
// Measures: process start → V8 init → Isolate → Context → eval → exit
|
||||
//
|
||||
// Build: cmake --build build/release --target bench-v8-e2e
|
||||
// Usage: ./bench-v8-e2e
|
||||
|
||||
#include <sys/resource.h>
|
||||
#include <v8.h>
|
||||
#include <libplatform/libplatform.h>
|
||||
#include <chrono>
|
||||
#include <cstdio>
|
||||
|
||||
using namespace v8;
|
||||
|
||||
class Timer {
|
||||
public:
|
||||
Timer() { start(); }
|
||||
void start() { start_ = std::chrono::high_resolution_clock::now(); }
|
||||
double elapsedMs() const {
|
||||
auto now = std::chrono::high_resolution_clock::now();
|
||||
return std::chrono::duration<double, std::milli>(now - start_).count();
|
||||
}
|
||||
private:
|
||||
std::chrono::high_resolution_clock::time_point start_;
|
||||
};
|
||||
|
||||
int main(int argc, char* argv[]) {
|
||||
Timer totalTimer;
|
||||
|
||||
// Initialize V8
|
||||
V8::InitializeICUDefaultLocation(argv[0]);
|
||||
V8::InitializeExternalStartupData(argv[0]);
|
||||
std::unique_ptr<Platform> platform = platform::NewDefaultPlatform();
|
||||
V8::InitializePlatform(platform.get());
|
||||
V8::Initialize();
|
||||
|
||||
double initTime = totalTimer.elapsedMs();
|
||||
|
||||
// Create Isolate
|
||||
Isolate::CreateParams create_params;
|
||||
create_params.array_buffer_allocator = ArrayBuffer::Allocator::NewDefaultAllocator();
|
||||
Isolate* isolate = Isolate::New(create_params);
|
||||
|
||||
double isolateTime = totalTimer.elapsedMs();
|
||||
|
||||
{
|
||||
Isolate::Scope isolate_scope(isolate);
|
||||
HandleScope handle_scope(isolate);
|
||||
|
||||
// Create Context
|
||||
Local<Context> context = Context::New(isolate);
|
||||
|
||||
double contextTime = totalTimer.elapsedMs();
|
||||
|
||||
Context::Scope context_scope(context);
|
||||
|
||||
// Eval simple script
|
||||
Local<String> source = String::NewFromUtf8Literal(isolate,
|
||||
"var x = 0; for (var i = 0; i < 1000; i++) x += i; x");
|
||||
|
||||
TryCatch try_catch(isolate);
|
||||
Local<Script> script;
|
||||
if (!Script::Compile(context, source).ToLocal(&script)) {
|
||||
String::Utf8Value error(isolate, try_catch.Exception());
|
||||
fprintf(stderr, "Compile error: %s\n", *error);
|
||||
return 1;
|
||||
}
|
||||
|
||||
Local<Value> result;
|
||||
if (!script->Run(context).ToLocal(&result)) {
|
||||
String::Utf8Value error(isolate, try_catch.Exception());
|
||||
fprintf(stderr, "Exception: %s\n", *error);
|
||||
return 1;
|
||||
}
|
||||
|
||||
double evalTime = totalTimer.elapsedMs();
|
||||
|
||||
// Get memory usage
|
||||
struct rusage usage;
|
||||
getrusage(RUSAGE_SELF, &usage);
|
||||
#ifdef __APPLE__
|
||||
double rssBytes = usage.ru_maxrss; // bytes on macOS
|
||||
#else
|
||||
double rssBytes = usage.ru_maxrss * 1024; // kilobytes on Linux
|
||||
#endif
|
||||
double rssMB = rssBytes / (1024 * 1024);
|
||||
|
||||
fprintf(stderr, "V8 E2E Cold Start:\n");
|
||||
fprintf(stderr, " Initialize: %6.3f ms\n", initTime);
|
||||
fprintf(stderr, " Isolate: %6.3f ms (+%.3f)\n", isolateTime, isolateTime - initTime);
|
||||
fprintf(stderr, " Context: %6.3f ms (+%.3f)\n", contextTime, contextTime - isolateTime);
|
||||
fprintf(stderr, " Eval: %6.3f ms (+%.3f)\n", evalTime, evalTime - contextTime);
|
||||
fprintf(stderr, " Total: %6.3f ms\n", evalTime);
|
||||
fprintf(stderr, " Peak RSS: %6.1f MB\n", rssMB);
|
||||
}
|
||||
|
||||
// Skip cleanup - just exit like JSC benchmarks do
|
||||
return 0;
|
||||
}
|
||||
136
misctools/bench-v8-multi-eval.cpp
Normal file
136
misctools/bench-v8-multi-eval.cpp
Normal file
@@ -0,0 +1,136 @@
|
||||
// V8 multi-eval benchmark - 1000 scripts in same Isolate
|
||||
//
|
||||
// Tests compile + eval performance for same vs varied scripts
|
||||
//
|
||||
// Build: cmake --build build/release --target bench-v8-multi-eval
|
||||
// Usage: ./bench-v8-multi-eval
|
||||
|
||||
#include <sys/resource.h>
|
||||
#include <v8.h>
|
||||
#include <libplatform/libplatform.h>
|
||||
#include <chrono>
|
||||
#include <cstdio>
|
||||
|
||||
using namespace v8;
|
||||
|
||||
class Timer {
|
||||
public:
|
||||
Timer() { start(); }
|
||||
void start() { start_ = std::chrono::high_resolution_clock::now(); }
|
||||
void reset() { start(); }
|
||||
double elapsedMs() const {
|
||||
auto now = std::chrono::high_resolution_clock::now();
|
||||
return std::chrono::duration<double, std::milli>(now - start_).count();
|
||||
}
|
||||
private:
|
||||
std::chrono::high_resolution_clock::time_point start_;
|
||||
};
|
||||
|
||||
static double getRSSMB() {
|
||||
struct rusage usage;
|
||||
getrusage(RUSAGE_SELF, &usage);
|
||||
#ifdef __APPLE__
|
||||
return usage.ru_maxrss / (1024.0 * 1024.0);
|
||||
#else
|
||||
return usage.ru_maxrss / 1024.0;
|
||||
#endif
|
||||
}
|
||||
|
||||
int main(int argc, char* argv[]) {
|
||||
constexpr int NUM_SCRIPTS = 1000;
|
||||
|
||||
// Initialize V8
|
||||
V8::InitializeICUDefaultLocation(argv[0]);
|
||||
V8::InitializeExternalStartupData(argv[0]);
|
||||
std::unique_ptr<Platform> platform = platform::NewDefaultPlatform();
|
||||
V8::InitializePlatform(platform.get());
|
||||
V8::Initialize();
|
||||
|
||||
// Create Isolate + Context
|
||||
Isolate::CreateParams create_params;
|
||||
create_params.array_buffer_allocator = ArrayBuffer::Allocator::NewDefaultAllocator();
|
||||
Isolate* isolate = Isolate::New(create_params);
|
||||
|
||||
{
|
||||
Isolate::Scope isolate_scope(isolate);
|
||||
HandleScope handle_scope(isolate);
|
||||
Local<Context> context = Context::New(isolate);
|
||||
Context::Scope context_scope(context);
|
||||
|
||||
// ============ SAME SCRIPT (1000x) ============
|
||||
{
|
||||
Timer timer;
|
||||
|
||||
const char* sameScript = "function compute(n) { var sum = 0; for (var j = 0; j < n; j++) sum += j; return sum; } compute(100)";
|
||||
|
||||
for (int i = 0; i < NUM_SCRIPTS; i++) {
|
||||
char nameBuf[32];
|
||||
snprintf(nameBuf, sizeof(nameBuf), "same_%d.js", i);
|
||||
|
||||
Local<String> source = String::NewFromUtf8(isolate, sameScript).ToLocalChecked();
|
||||
ScriptOrigin origin(String::NewFromUtf8(isolate, nameBuf).ToLocalChecked());
|
||||
|
||||
TryCatch try_catch(isolate);
|
||||
Local<Script> script;
|
||||
if (!Script::Compile(context, source, &origin).ToLocal(&script)) {
|
||||
String::Utf8Value error(isolate, try_catch.Exception());
|
||||
fprintf(stderr, "Compile error in same script %d: %s\n", i, *error);
|
||||
return 1;
|
||||
}
|
||||
|
||||
Local<Value> result;
|
||||
if (!script->Run(context).ToLocal(&result)) {
|
||||
String::Utf8Value error(isolate, try_catch.Exception());
|
||||
fprintf(stderr, "Exception in same script %d: %s\n", i, *error);
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
double totalTime = timer.elapsedMs();
|
||||
double rssMB = getRSSMB();
|
||||
|
||||
fprintf(stderr, "same_script: %8.3f ms %6.1f MB\n", totalTime, rssMB);
|
||||
}
|
||||
|
||||
// ============ DIFFERENT SCRIPTS (1000x) ============
|
||||
{
|
||||
Timer timer;
|
||||
|
||||
for (int i = 0; i < NUM_SCRIPTS; i++) {
|
||||
char scriptBuf[256];
|
||||
snprintf(scriptBuf, sizeof(scriptBuf),
|
||||
"function compute_%d(n) { var sum = %d; for (var j = 0; j < n; j++) sum += j * %d; return sum; } compute_%d(100)",
|
||||
i, i, i + 1, i);
|
||||
|
||||
char nameBuf[32];
|
||||
snprintf(nameBuf, sizeof(nameBuf), "diff_%d.js", i);
|
||||
|
||||
Local<String> source = String::NewFromUtf8(isolate, scriptBuf).ToLocalChecked();
|
||||
ScriptOrigin origin(String::NewFromUtf8(isolate, nameBuf).ToLocalChecked());
|
||||
|
||||
TryCatch try_catch(isolate);
|
||||
Local<Script> script;
|
||||
if (!Script::Compile(context, source, &origin).ToLocal(&script)) {
|
||||
String::Utf8Value error(isolate, try_catch.Exception());
|
||||
fprintf(stderr, "Compile error in diff script %d: %s\n", i, *error);
|
||||
return 1;
|
||||
}
|
||||
|
||||
Local<Value> result;
|
||||
if (!script->Run(context).ToLocal(&result)) {
|
||||
String::Utf8Value error(isolate, try_catch.Exception());
|
||||
fprintf(stderr, "Exception in diff script %d: %s\n", i, *error);
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
double totalTime = timer.elapsedMs();
|
||||
double rssMB = getRSSMB();
|
||||
|
||||
fprintf(stderr, "diff_script: %8.3f ms %6.1f MB\n", totalTime, rssMB);
|
||||
}
|
||||
}
|
||||
|
||||
// Skip cleanup - just exit like JSC benchmarks do
|
||||
return 0;
|
||||
}
|
||||
@@ -5,27 +5,60 @@
|
||||
// This loads up a JavaScriptCore global object which only has a "write"
|
||||
// global function and then calls eval
|
||||
//
|
||||
//
|
||||
// Usage:
|
||||
// ./cold-jsc-start <file>
|
||||
// ./cold-jsc-start -e "write('hey')"
|
||||
//
|
||||
// Build with: cmake --build build/debug --target cold-jsc-start
|
||||
//
|
||||
|
||||
// Fix WebCore feature mismatches - must be set before root.h
|
||||
#define ENABLE_COCOA_WEBM_PLAYER 0
|
||||
|
||||
#include "root.h"
|
||||
|
||||
#include <wtf/FileSystem.h>
|
||||
|
||||
#include <JavaScriptCore/JSGlobalObject.h>
|
||||
|
||||
#include <JavaScriptCore/JSArrayBufferView.h>
|
||||
#include <JavaScriptCore/JSArrayBufferViewInlines.h>
|
||||
|
||||
#include <JavaScriptCore/Completion.h>
|
||||
#include <JavaScriptCore/InitializeThreading.h>
|
||||
#include <JavaScriptCore/JSCConfig.h>
|
||||
#include <unistd.h>
|
||||
#include <wtf/Stopwatch.h>
|
||||
#include <wtf/Threading.h>
|
||||
|
||||
using namespace JSC;
|
||||
|
||||
// Minimal VM::ClientData
|
||||
class MinimalClientData : public JSC::VM::ClientData {
|
||||
public:
|
||||
MinimalClientData() = default;
|
||||
virtual ~MinimalClientData() = default;
|
||||
|
||||
WTF::String overrideSourceURL(const JSC::StackFrame&, const WTF::String& originalSourceURL) const override
|
||||
{
|
||||
return originalSourceURL;
|
||||
}
|
||||
};
|
||||
|
||||
// Stub implementations for Bun-specific WebKit hooks
|
||||
extern "C" {
|
||||
void Bun__errorInstance__finalize(void*) {}
|
||||
|
||||
// Minimal timer stub - returns a dummy non-null pointer
|
||||
static char dummyTimer = 0;
|
||||
void* WTFTimer__create(void*, void*, void*) { return &dummyTimer; }
|
||||
void WTFTimer__deinit(void*) {}
|
||||
void WTFTimer__cancel(void*) {}
|
||||
void WTFTimer__update(void*, double, bool) {}
|
||||
bool WTFTimer__isActive(void*) { return false; }
|
||||
double WTFTimer__secondsUntilTimer(void*) { return 0.0; }
|
||||
|
||||
// Stub for Bun's VM - returns null since we don't have a real Bun VM
|
||||
void* Bun__getVM() { return nullptr; }
|
||||
}
|
||||
|
||||
JSC_DEFINE_HOST_FUNCTION(jsFunctionWrite, (JSC::JSGlobalObject * globalObject,
|
||||
JSC::CallFrame *callframe)) {
|
||||
|
||||
@@ -65,46 +98,117 @@ int main(int argc, char **argv) {
|
||||
fprintf(stderr, "Usage: %s <file>\n", argv[0]);
|
||||
return 1;
|
||||
}
|
||||
|
||||
// Check for --benchmark-vm flag
|
||||
bool benchmarkVM = false;
|
||||
for (int i = 1; i < argc; i++) {
|
||||
if (strcmp(argv[i], "--benchmark-vm") == 0) {
|
||||
benchmarkVM = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Must call WTF::initialize() first, before anything else
|
||||
WTF::initialize();
|
||||
|
||||
#ifdef VERBOSE
|
||||
auto stopwatch = Stopwatch::create();
|
||||
stopwatch->start();
|
||||
#endif
|
||||
|
||||
{
|
||||
JSC::Config::enableRestrictedOptions();
|
||||
WTF::initializeMainThread();
|
||||
JSC::initialize();
|
||||
{
|
||||
JSC::Options::AllowUnfinalizedAccessScope scope;
|
||||
|
||||
JSC::Options::useConcurrentJIT() = true;
|
||||
// JSC::Options::useSigillCrashAnalyzer() = true;
|
||||
JSC::Options::useWebAssembly() = true;
|
||||
JSC::Options::useSourceProviderCache() = true;
|
||||
// JSC::Options::useUnlinkedCodeBlockJettisoning() = false;
|
||||
JSC::Options::exposeInternalModuleLoader() = true;
|
||||
JSC::Options::useSharedArrayBuffer() = true;
|
||||
JSC::Options::useJIT() = true;
|
||||
JSC::Options::useBBQJIT() = true;
|
||||
JSC::Options::useJITCage() = false;
|
||||
JSC::Options::useShadowRealm() = true;
|
||||
JSC::Options::useResizableArrayBuffer() = true;
|
||||
JSC::Options::showPrivateScriptsInStackTraces() = true;
|
||||
JSC::Options::useSetMethods() = true;
|
||||
JSC::Options::useWasm() = true;
|
||||
JSC::Options::assertOptionsAreCoherent();
|
||||
}
|
||||
}
|
||||
|
||||
#ifdef VERBOSE
|
||||
|
||||
fprintf(stderr, "JSC::Initialize took %f ms\n",
|
||||
stopwatch->elapsedTime().milliseconds());
|
||||
stopwatch->reset();
|
||||
stopwatch->start();
|
||||
#endif
|
||||
|
||||
auto &vm = JSC::VM::create(JSC::HeapType::Large).leakRef();
|
||||
// Benchmark mode: create 100 VMs, then 100 GlobalObjects
|
||||
if (benchmarkVM) {
|
||||
constexpr int NUM_VMS = 100;
|
||||
JSC::VM* vms[NUM_VMS];
|
||||
JSC::JSGlobalObject* globalObjects[NUM_VMS];
|
||||
|
||||
// First benchmark: Create 100 VMs
|
||||
auto benchStart = Stopwatch::create();
|
||||
benchStart->start();
|
||||
|
||||
for (int i = 0; i < NUM_VMS; i++) {
|
||||
auto vmPtr = JSC::VM::tryCreate(JSC::HeapType::Large);
|
||||
if (!vmPtr) {
|
||||
fprintf(stderr, "Failed to create VM %d\n", i);
|
||||
return 1;
|
||||
}
|
||||
vmPtr->refSuppressingSaferCPPChecking();
|
||||
vms[i] = vmPtr.get();
|
||||
vms[i]->heap.acquireAccess();
|
||||
}
|
||||
|
||||
double vmTime = benchStart->elapsedTime().milliseconds();
|
||||
fprintf(stderr, "Created %d VMs in %f ms (%f ms per VM)\n",
|
||||
NUM_VMS, vmTime, vmTime / NUM_VMS);
|
||||
|
||||
// Second benchmark: Create 100 GlobalObjects on existing VMs
|
||||
benchStart->reset();
|
||||
benchStart->start();
|
||||
|
||||
for (int i = 0; i < NUM_VMS; i++) {
|
||||
JSC::JSLockHolder locker(*vms[i]);
|
||||
vms[i]->clientData = new MinimalClientData();
|
||||
auto* structure = JSC::JSGlobalObject::createStructure(*vms[i], JSC::jsNull());
|
||||
globalObjects[i] = JSC::JSGlobalObject::create(*vms[i], structure);
|
||||
JSC::gcProtect(globalObjects[i]);
|
||||
}
|
||||
|
||||
double globalObjectTime = benchStart->elapsedTime().milliseconds();
|
||||
fprintf(stderr, "Created %d GlobalObjects in %f ms (%f ms per GlobalObject)\n",
|
||||
NUM_VMS, globalObjectTime, globalObjectTime / NUM_VMS);
|
||||
|
||||
fprintf(stderr, "Total: %f ms (%f ms per VM+GlobalObject)\n",
|
||||
vmTime + globalObjectTime, (vmTime + globalObjectTime) / NUM_VMS);
|
||||
|
||||
// Keep VMs alive - don't destruct
|
||||
return 0;
|
||||
}
|
||||
|
||||
// 1. Create VM
|
||||
auto vmPtr = JSC::VM::tryCreate(JSC::HeapType::Large);
|
||||
if (!vmPtr) {
|
||||
fprintf(stderr, "Failed to create VM\n");
|
||||
return 1;
|
||||
}
|
||||
vmPtr->refSuppressingSaferCPPChecking();
|
||||
auto &vm = *vmPtr;
|
||||
|
||||
// 2. Acquire heap access (must happen before JSVMClientData::create per Bun)
|
||||
vm.heap.acquireAccess();
|
||||
|
||||
// 3. Lock
|
||||
JSC::JSLockHolder locker(vm);
|
||||
|
||||
// 4. Set up client data
|
||||
vm.clientData = new MinimalClientData();
|
||||
|
||||
#ifdef VERBOSE
|
||||
fprintf(stderr, "JSC::VM::create took %f ms\n",
|
||||
stopwatch->elapsedTime().milliseconds());
|
||||
@@ -112,9 +216,11 @@ int main(int argc, char **argv) {
|
||||
stopwatch->start();
|
||||
#endif
|
||||
|
||||
JSC::JSLockHolder locker(vm);
|
||||
auto *globalObject = JSC::JSGlobalObject::create(
|
||||
vm, JSC::JSGlobalObject::createStructure(vm, JSC::jsNull()));
|
||||
// 5. Create structure
|
||||
auto* structure = JSC::JSGlobalObject::createStructure(vm, JSC::jsNull());
|
||||
|
||||
// 6. Create GlobalObject
|
||||
auto *globalObject = JSC::JSGlobalObject::create(vm, structure);
|
||||
|
||||
#ifdef VERBOSE
|
||||
fprintf(stderr, "JSC::JSGlobalObject::create took %f ms\n",
|
||||
@@ -129,8 +235,6 @@ int main(int argc, char **argv) {
|
||||
PropertyName(JSC::Identifier::fromString(vm, "write"_s)), 0,
|
||||
jsFunctionWrite, ImplementationVisibility::Public, JSC::NoIntrinsic,
|
||||
JSC::PropertyAttribute::ReadOnly | 0);
|
||||
|
||||
vm.ref();
|
||||
if (argc > 2) {
|
||||
auto source =
|
||||
JSC::makeSource(WTF::String::fromUTF8(argv[argc - 1]),
|
||||
|
||||
223
misctools/cold-v8-start.cpp
Normal file
223
misctools/cold-v8-start.cpp
Normal file
@@ -0,0 +1,223 @@
|
||||
// V8 Isolate cold start benchmark - comparable to cold-jsc-start.cpp
|
||||
//
|
||||
// Usage:
|
||||
// ./cold-v8-start <file>
|
||||
// ./cold-v8-start -e "print('hey')"
|
||||
// ./cold-v8-start --benchmark-isolate # Create 100 isolates
|
||||
//
|
||||
// Build with: cmake --build build/release --target cold-v8-start
|
||||
|
||||
#include <v8.h>
|
||||
#include <libplatform/libplatform.h>
|
||||
#include <cstdio>
|
||||
#include <cstring>
|
||||
#include <chrono>
|
||||
#include <fstream>
|
||||
#include <sstream>
|
||||
#include <unistd.h>
|
||||
|
||||
using namespace v8;
|
||||
|
||||
// Simple high-resolution timer
|
||||
class Timer {
|
||||
public:
|
||||
void start() { start_ = std::chrono::high_resolution_clock::now(); }
|
||||
void reset() { start(); }
|
||||
double elapsedMs() const {
|
||||
auto now = std::chrono::high_resolution_clock::now();
|
||||
return std::chrono::duration<double, std::milli>(now - start_).count();
|
||||
}
|
||||
private:
|
||||
std::chrono::high_resolution_clock::time_point start_;
|
||||
};
|
||||
|
||||
// Print function for JavaScript
|
||||
static void Print(const FunctionCallbackInfo<Value>& args) {
|
||||
bool first = true;
|
||||
for (int i = 0; i < args.Length(); i++) {
|
||||
HandleScope handle_scope(args.GetIsolate());
|
||||
if (first) {
|
||||
first = false;
|
||||
} else {
|
||||
printf(" ");
|
||||
}
|
||||
String::Utf8Value str(args.GetIsolate(), args[i]);
|
||||
printf("%s", *str ? *str : "<string conversion failed>");
|
||||
}
|
||||
fflush(stdout);
|
||||
}
|
||||
|
||||
// Write function matching JSC version
|
||||
static void Write(const FunctionCallbackInfo<Value>& args) {
|
||||
Isolate* isolate = args.GetIsolate();
|
||||
|
||||
if (args.Length() < 1) {
|
||||
args.GetReturnValue().Set(Undefined(isolate));
|
||||
return;
|
||||
}
|
||||
|
||||
int fd = STDOUT_FILENO;
|
||||
Local<Value> toWrite;
|
||||
|
||||
if (args.Length() > 1) {
|
||||
fd = args[0]->Int32Value(isolate->GetCurrentContext()).FromMaybe(STDOUT_FILENO);
|
||||
toWrite = args[1];
|
||||
} else {
|
||||
toWrite = args[0];
|
||||
}
|
||||
|
||||
String::Utf8Value str(isolate, toWrite);
|
||||
if (*str) {
|
||||
ssize_t written = write(fd, *str, str.length());
|
||||
args.GetReturnValue().Set(Number::New(isolate, static_cast<double>(written)));
|
||||
} else {
|
||||
args.GetReturnValue().Set(Number::New(isolate, 0));
|
||||
}
|
||||
}
|
||||
|
||||
int main(int argc, char* argv[]) {
|
||||
if (argc < 2) {
|
||||
fprintf(stderr, "Usage: %s <file>\n", argv[0]);
|
||||
fprintf(stderr, " %s -e \"code\"\n", argv[0]);
|
||||
fprintf(stderr, " %s --benchmark-isolate\n", argv[0]);
|
||||
return 1;
|
||||
}
|
||||
|
||||
// Check for --benchmark-isolate flag
|
||||
bool benchmarkIsolate = false;
|
||||
for (int i = 1; i < argc; i++) {
|
||||
if (strcmp(argv[i], "--benchmark-isolate") == 0) {
|
||||
benchmarkIsolate = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
Timer timer;
|
||||
timer.start();
|
||||
|
||||
// Initialize V8
|
||||
V8::InitializeICUDefaultLocation(argv[0]);
|
||||
V8::InitializeExternalStartupData(argv[0]);
|
||||
std::unique_ptr<Platform> platform = platform::NewDefaultPlatform();
|
||||
V8::InitializePlatform(platform.get());
|
||||
V8::Initialize();
|
||||
|
||||
fprintf(stderr, "V8::Initialize took %f ms\n", timer.elapsedMs());
|
||||
timer.reset();
|
||||
|
||||
// Benchmark mode: create 100 isolates, then 100 contexts
|
||||
if (benchmarkIsolate) {
|
||||
constexpr int NUM_ISOLATES = 100;
|
||||
Isolate* isolates[NUM_ISOLATES];
|
||||
Global<Context>* contexts[NUM_ISOLATES];
|
||||
|
||||
Isolate::CreateParams create_params;
|
||||
create_params.array_buffer_allocator = ArrayBuffer::Allocator::NewDefaultAllocator();
|
||||
|
||||
// First benchmark: Create 100 Isolates
|
||||
Timer benchTimer;
|
||||
benchTimer.start();
|
||||
|
||||
for (int i = 0; i < NUM_ISOLATES; i++) {
|
||||
isolates[i] = Isolate::New(create_params);
|
||||
if (!isolates[i]) {
|
||||
fprintf(stderr, "Failed to create isolate %d\n", i);
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
double isolateTime = benchTimer.elapsedMs();
|
||||
fprintf(stderr, "Created %d Isolates in %f ms (%f ms per Isolate)\n",
|
||||
NUM_ISOLATES, isolateTime, isolateTime / NUM_ISOLATES);
|
||||
|
||||
// Second benchmark: Create 100 Contexts on existing Isolates
|
||||
benchTimer.reset();
|
||||
|
||||
for (int i = 0; i < NUM_ISOLATES; i++) {
|
||||
Isolate::Scope isolate_scope(isolates[i]);
|
||||
HandleScope handle_scope(isolates[i]);
|
||||
Local<Context> context = Context::New(isolates[i]);
|
||||
contexts[i] = new Global<Context>(isolates[i], context);
|
||||
}
|
||||
|
||||
double contextTime = benchTimer.elapsedMs();
|
||||
fprintf(stderr, "Created %d Contexts in %f ms (%f ms per Context)\n",
|
||||
NUM_ISOLATES, contextTime, contextTime / NUM_ISOLATES);
|
||||
|
||||
fprintf(stderr, "Total: %f ms (%f ms per Isolate+Context)\n",
|
||||
isolateTime + contextTime, (isolateTime + contextTime) / NUM_ISOLATES);
|
||||
|
||||
// Keep isolates alive - don't dispose
|
||||
delete create_params.array_buffer_allocator;
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Create isolate
|
||||
Isolate::CreateParams create_params;
|
||||
create_params.array_buffer_allocator = ArrayBuffer::Allocator::NewDefaultAllocator();
|
||||
Isolate* isolate = Isolate::New(create_params);
|
||||
|
||||
fprintf(stderr, "Isolate::New took %f ms\n", timer.elapsedMs());
|
||||
timer.reset();
|
||||
|
||||
{
|
||||
Isolate::Scope isolate_scope(isolate);
|
||||
HandleScope handle_scope(isolate);
|
||||
|
||||
// Create global template with write function
|
||||
Local<ObjectTemplate> global = ObjectTemplate::New(isolate);
|
||||
global->Set(isolate, "write", FunctionTemplate::New(isolate, Write));
|
||||
global->Set(isolate, "print", FunctionTemplate::New(isolate, Print));
|
||||
|
||||
// Create context
|
||||
Local<Context> context = Context::New(isolate, nullptr, global);
|
||||
|
||||
fprintf(stderr, "Context::New took %f ms\n", timer.elapsedMs());
|
||||
timer.reset();
|
||||
|
||||
Context::Scope context_scope(context);
|
||||
|
||||
// Get source code
|
||||
std::string source;
|
||||
const char* sourceOrigin = "eval.js";
|
||||
|
||||
if (argc > 2 && strcmp(argv[1], "-e") == 0) {
|
||||
source = argv[argc - 1];
|
||||
} else {
|
||||
std::ifstream file(argv[argc - 1]);
|
||||
if (!file) {
|
||||
fprintf(stderr, "Could not read file %s\n", argv[argc - 1]);
|
||||
return 1;
|
||||
}
|
||||
std::stringstream buffer;
|
||||
buffer << file.rdbuf();
|
||||
source = buffer.str();
|
||||
sourceOrigin = argv[argc - 1];
|
||||
}
|
||||
|
||||
// Compile and run
|
||||
Local<String> source_str = String::NewFromUtf8(isolate, source.c_str()).ToLocalChecked();
|
||||
ScriptOrigin origin(String::NewFromUtf8(isolate, sourceOrigin).ToLocalChecked());
|
||||
|
||||
TryCatch try_catch(isolate);
|
||||
|
||||
Local<Script> script;
|
||||
if (!Script::Compile(context, source_str, &origin).ToLocal(&script)) {
|
||||
String::Utf8Value error(isolate, try_catch.Exception());
|
||||
fprintf(stderr, "Compile error: %s\n", *error);
|
||||
return 1;
|
||||
}
|
||||
|
||||
Local<Value> result;
|
||||
if (!script->Run(context).ToLocal(&result)) {
|
||||
String::Utf8Value error(isolate, try_catch.Exception());
|
||||
fprintf(stderr, "Exception: %s\n", *error);
|
||||
return 1;
|
||||
}
|
||||
|
||||
fprintf(stderr, "\neval took %f ms\n", timer.elapsedMs());
|
||||
}
|
||||
|
||||
// Skip cleanup - just exit like JSC benchmarks do
|
||||
return 0;
|
||||
}
|
||||
@@ -93,6 +93,13 @@
|
||||
"machine:linux:amazonlinux": "./scripts/machine.mjs ssh --cloud=aws --arch=x64 --instance-type c7i.2xlarge --os=linux --distro=amazonlinux --release=2023",
|
||||
"machine:windows:2019": "./scripts/machine.mjs ssh --cloud=aws --arch=x64 --instance-type c7i.2xlarge --os=windows --release=2019",
|
||||
"machine:freebsd": "./scripts/machine.mjs ssh --cloud=aws --arch=x64 --instance-type c7i.2xlarge --os=freebsd --release=14.3",
|
||||
"sync-webkit-source": "bun ./scripts/sync-webkit-source.ts"
|
||||
"sync-webkit-source": "bun ./scripts/sync-webkit-source.ts",
|
||||
"bench:coldstart": "bun run bench:coldstart:build && bun run bench:coldstart:run",
|
||||
"bench:coldstart:build": "cmake --build build/release --target cold-jsc-start cold-v8-start bench-jsc-e2e bench-v8-e2e bench-jsc-100-e2e bench-v8-100-e2e bench-jsc-multi-eval bench-v8-multi-eval 2>&1 | tail -5",
|
||||
"bench:coldstart:run": "bun run bench:coldstart:e2e && bun run bench:coldstart:100 && bun run bench:coldstart:multi",
|
||||
"bench:coldstart:e2e": "echo '=== Single E2E Cold Start ===' && echo '--- JSC ---' && ./build/release/bench-jsc-e2e && echo '' && echo '--- V8 ---' && ./build/release/bench-v8-e2e",
|
||||
"bench:coldstart:100": "echo '=== 100 Full E2E (VM + GlobalObject + eval) ===' && echo '--- JSC ---' && ./build/release/bench-jsc-100-e2e && echo '' && echo '--- V8 ---' && ./build/release/bench-v8-100-e2e",
|
||||
"bench:coldstart:multi": "echo '=== 100 Scripts in Same VM ===' && echo '--- JSC ---' && ./build/release/bench-jsc-multi-eval && echo '' && echo '--- V8 ---' && ./build/release/bench-v8-multi-eval",
|
||||
"bench:coldstart:vm": "echo '=== 100 VMs/Isolates Batch ===' && echo '--- JSC ---' && ./build/release/cold-jsc-start --benchmark-vm && echo '' && echo '--- V8 ---' && ./build/release/cold-v8-start --benchmark-isolate"
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user