Compare commits

...

2 Commits

Author SHA1 Message Date
Jarred Sumner
25381f1ad2 wip 2025-12-19 01:20:55 +01:00
Jarred Sumner
806e253a89 Update misctools/cold-jsc-start.cpp 2025-12-18 14:53:17 -08:00
14 changed files with 1727 additions and 18 deletions

View File

@@ -63,6 +63,7 @@ include(GenerateDependencyVersions)
# --- Targets ---
include(BuildBun)
include(BuildMisctools)
# --- Analysis ---

View 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()

View 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

View 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;
}

View 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
View 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;
}

View 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;
}

View 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;
}

View 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
View 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;
}

View 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;
}

View File

@@ -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
View 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;
}

View File

@@ -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"
}
}