mirror of
https://github.com/simdjson/simdjson
synced 2026-06-08 17:27:07 +00:00
352 lines
10 KiB
Rust
352 lines
10 KiB
Rust
extern crate serde;
|
|
extern crate serde_json;
|
|
extern crate libc;
|
|
|
|
use libc::{c_char, size_t};
|
|
use serde::{Serialize, Deserialize};
|
|
use std::{collections::HashMap, ffi::CString, ptr, slice};
|
|
|
|
//==============================================================================
|
|
// Twitter Benchmark Structures
|
|
// These match the C++ TwitterData structures exactly
|
|
//==============================================================================
|
|
|
|
#[derive(Serialize, Deserialize)]
|
|
pub struct User {
|
|
id: u64,
|
|
name: String,
|
|
screen_name: String,
|
|
location: String,
|
|
description: String,
|
|
verified: bool,
|
|
followers_count: u64,
|
|
friends_count: u64,
|
|
statuses_count: u64,
|
|
}
|
|
|
|
#[derive(Serialize, Deserialize)]
|
|
pub struct Status {
|
|
created_at: String,
|
|
id: u64,
|
|
text: String,
|
|
user: User,
|
|
retweet_count: u64,
|
|
favorite_count: u64,
|
|
}
|
|
|
|
#[derive(Serialize, Deserialize)]
|
|
pub struct TwitterData {
|
|
statuses: Vec<Status>,
|
|
}
|
|
static mut TWITTER_DATA: *mut TwitterData = std::ptr::null_mut();
|
|
|
|
#[no_mangle]
|
|
pub unsafe extern "C" fn twitter_from_str(raw_input: *const c_char, raw_input_length: size_t) -> *mut TwitterData {
|
|
let input = std::str::from_utf8_unchecked(slice::from_raw_parts(raw_input as *const u8, raw_input_length));
|
|
match serde_json::from_str(&input) {
|
|
Ok(result) => Box::into_raw(Box::new(result)),
|
|
Err(_) => std::ptr::null_mut(),
|
|
}
|
|
}
|
|
|
|
#[no_mangle]
|
|
pub unsafe extern "C" fn set_twitter_data(raw: *mut TwitterData) {
|
|
TWITTER_DATA = raw;
|
|
}
|
|
|
|
#[no_mangle]
|
|
pub unsafe extern "C" fn serialize_twitter_to_string() -> usize {
|
|
if TWITTER_DATA.is_null() {
|
|
return 0;
|
|
}
|
|
let data = &*TWITTER_DATA;
|
|
serde_json::to_string(data).unwrap().len()
|
|
}
|
|
|
|
#[no_mangle]
|
|
pub unsafe extern "C" fn free_twitter(raw: *mut TwitterData) {
|
|
if raw.is_null() {
|
|
return;
|
|
}
|
|
drop(Box::from_raw(raw))
|
|
}
|
|
|
|
#[no_mangle]
|
|
pub unsafe extern fn free_string(ptr: *const c_char) {
|
|
let _ = std::ffi::CString::from_raw(ptr as *mut _);
|
|
}
|
|
|
|
//==============================================================================
|
|
// CITM Catalog Benchmark Structures
|
|
// These match the C++ CitmCatalog structures EXACTLY for fair comparison
|
|
//==============================================================================
|
|
|
|
/// Matches C++ CITMPrice struct exactly
|
|
#[derive(Serialize, Deserialize, Debug, Clone)]
|
|
pub struct CITMPrice {
|
|
pub amount: u64,
|
|
#[serde(rename = "audienceSubCategoryId")]
|
|
pub audience_sub_category_id: u64,
|
|
#[serde(rename = "seatCategoryId")]
|
|
pub seat_category_id: u64,
|
|
}
|
|
|
|
/// Matches C++ CITMArea struct exactly
|
|
#[derive(Serialize, Deserialize, Debug, Clone)]
|
|
pub struct CITMArea {
|
|
#[serde(rename = "areaId")]
|
|
pub area_id: u64,
|
|
#[serde(rename = "blockIds")]
|
|
pub block_ids: Vec<u64>,
|
|
}
|
|
|
|
/// Matches C++ CITMSeatCategory struct exactly
|
|
#[derive(Serialize, Deserialize, Debug, Clone)]
|
|
pub struct CITMSeatCategory {
|
|
pub areas: Vec<CITMArea>,
|
|
#[serde(rename = "seatCategoryId")]
|
|
pub seat_category_id: u64,
|
|
}
|
|
|
|
/// Matches C++ CITMPerformance struct exactly
|
|
#[derive(Serialize, Deserialize, Debug, Clone)]
|
|
pub struct CITMPerformance {
|
|
pub id: u64,
|
|
#[serde(rename = "eventId")]
|
|
pub event_id: u64,
|
|
#[serde(default)]
|
|
pub logo: Option<String>,
|
|
#[serde(default)]
|
|
pub name: Option<String>,
|
|
pub prices: Vec<CITMPrice>,
|
|
#[serde(rename = "seatCategories")]
|
|
pub seat_categories: Vec<CITMSeatCategory>,
|
|
#[serde(default)]
|
|
#[serde(rename = "seatMapImage")]
|
|
pub seat_map_image: Option<String>,
|
|
pub start: u64,
|
|
#[serde(rename = "venueCode")]
|
|
pub venue_code: String,
|
|
}
|
|
|
|
/// Matches C++ CITMEvent struct exactly
|
|
#[derive(Serialize, Deserialize, Debug, Clone)]
|
|
pub struct CITMEvent {
|
|
pub id: u64,
|
|
#[serde(default)]
|
|
pub name: Option<String>,
|
|
#[serde(default)]
|
|
pub description: Option<String>,
|
|
#[serde(default)]
|
|
pub logo: Option<String>,
|
|
#[serde(default)]
|
|
#[serde(rename = "subTopicIds")]
|
|
pub sub_topic_ids: Vec<u64>,
|
|
#[serde(default)]
|
|
#[serde(rename = "subjectCode")]
|
|
pub subject_code: Option<String>,
|
|
#[serde(default)]
|
|
pub subtitle: Option<String>,
|
|
#[serde(default)]
|
|
#[serde(rename = "topicIds")]
|
|
pub topic_ids: Vec<u64>,
|
|
}
|
|
|
|
/// Matches C++ CitmCatalog struct exactly - ONLY events and performances
|
|
/// This is the key fix: we serialize only what C++ serializes
|
|
#[derive(Serialize, Deserialize, Debug)]
|
|
pub struct CitmCatalog {
|
|
pub events: HashMap<String, CITMEvent>,
|
|
pub performances: Vec<CITMPerformance>,
|
|
}
|
|
|
|
static mut CITM_DATA: *mut CitmCatalog = std::ptr::null_mut();
|
|
|
|
/// Creates a CitmCatalog from a JSON string (UTF-8 encoded).
|
|
/// Only extracts events and performances to match C++ behavior.
|
|
#[no_mangle]
|
|
pub unsafe extern "C" fn citm_from_str(
|
|
raw_input: *const c_char,
|
|
raw_input_length: usize
|
|
) -> *mut CitmCatalog {
|
|
if raw_input.is_null() {
|
|
eprintln!("Error: Input pointer is null");
|
|
return ptr::null_mut();
|
|
}
|
|
|
|
let bytes = slice::from_raw_parts(raw_input as *const u8, raw_input_length);
|
|
let input_str = match std::str::from_utf8(bytes) {
|
|
Ok(s) => s,
|
|
Err(e) => {
|
|
eprintln!("Error: Invalid UTF-8 string: {}", e);
|
|
return ptr::null_mut();
|
|
}
|
|
};
|
|
|
|
// Parse the full JSON to extract only events and performances
|
|
match serde_json::from_str::<serde_json::Value>(input_str) {
|
|
Ok(full_json) => {
|
|
// Extract only the fields we need (matching C++ behavior)
|
|
let events: HashMap<String, CITMEvent> = full_json.get("events")
|
|
.and_then(|v| serde_json::from_value(v.clone()).ok())
|
|
.unwrap_or_default();
|
|
|
|
let performances: Vec<CITMPerformance> = full_json.get("performances")
|
|
.and_then(|v| serde_json::from_value(v.clone()).ok())
|
|
.unwrap_or_default();
|
|
|
|
let catalog = CitmCatalog { events, performances };
|
|
Box::into_raw(Box::new(catalog))
|
|
},
|
|
Err(e) => {
|
|
eprintln!("Error deserializing JSON: {}", e);
|
|
ptr::null_mut()
|
|
}
|
|
}
|
|
}
|
|
|
|
/// Serializes a CitmCatalog into a JSON string (UTF-8).
|
|
#[no_mangle]
|
|
pub unsafe extern "C" fn set_citm_data(raw: *mut CitmCatalog) {
|
|
CITM_DATA = raw;
|
|
}
|
|
|
|
#[no_mangle]
|
|
pub unsafe extern "C" fn serialize_citm_to_string() -> usize {
|
|
if CITM_DATA.is_null() {
|
|
return 0;
|
|
}
|
|
let data = &*CITM_DATA;
|
|
return serde_json::to_string(data).unwrap().len();
|
|
}
|
|
|
|
/// Frees the CitmCatalog pointer.
|
|
#[no_mangle]
|
|
pub unsafe extern "C" fn free_citm(raw_catalog: *mut CitmCatalog) {
|
|
if !raw_catalog.is_null() {
|
|
drop(Box::from_raw(raw_catalog));
|
|
}
|
|
}
|
|
|
|
#[no_mangle]
|
|
pub extern "C" fn free_str(ptr: *mut c_char) {
|
|
if !ptr.is_null() {
|
|
unsafe {
|
|
let _ = CString::from_raw(ptr);
|
|
}
|
|
}
|
|
}
|
|
|
|
//==============================================================================
|
|
// FFI Overhead Measurement Functions
|
|
// These allow measuring the actual FFI overhead vs pure Rust serialization
|
|
//==============================================================================
|
|
|
|
/// Result structure for FFI overhead measurement
|
|
#[repr(C)]
|
|
pub struct FfiOverheadResult {
|
|
/// Time in nanoseconds for pure serde_json::to_string() (no FFI overhead)
|
|
pub pure_serde_ns: u64,
|
|
/// Time in nanoseconds for serde + CString conversion
|
|
pub serde_plus_cstring_ns: u64,
|
|
/// Number of iterations performed
|
|
pub iterations: u64,
|
|
/// Output size in bytes (for verification)
|
|
pub output_size: u64,
|
|
}
|
|
|
|
/// Prevents compiler from optimizing away the value
|
|
/// Works on stable Rust (unlike std::hint::black_box which is unstable)
|
|
#[inline(never)]
|
|
fn black_box<T>(dummy: T) -> T {
|
|
unsafe {
|
|
let ret = std::ptr::read_volatile(&dummy);
|
|
std::mem::forget(dummy);
|
|
ret
|
|
}
|
|
}
|
|
|
|
/// Measures FFI overhead for Twitter serialization.
|
|
/// Performs `iterations` serializations entirely in Rust and returns timing data.
|
|
/// This allows comparing against per-call FFI overhead.
|
|
#[no_mangle]
|
|
pub unsafe extern "C" fn measure_twitter_ffi_overhead(
|
|
raw: *mut TwitterData,
|
|
iterations: u64
|
|
) -> FfiOverheadResult {
|
|
use std::time::Instant;
|
|
|
|
let twitter_data = &*raw;
|
|
let output_size: u64;
|
|
|
|
// Warm-up run
|
|
let warmup = serde_json::to_string(&twitter_data).unwrap();
|
|
output_size = warmup.len() as u64;
|
|
|
|
// Measure pure serde_json::to_string() - no CString conversion
|
|
let start_pure = Instant::now();
|
|
for _ in 0..iterations {
|
|
let serialized = serde_json::to_string(&twitter_data).unwrap();
|
|
// Prevent optimization from eliminating the work
|
|
black_box(&serialized);
|
|
}
|
|
let pure_serde_ns = start_pure.elapsed().as_nanos() as u64;
|
|
|
|
// Measure serde + CString conversion (but not FFI return)
|
|
let start_cstring = Instant::now();
|
|
for _ in 0..iterations {
|
|
let serialized = serde_json::to_string(&twitter_data).unwrap();
|
|
let cstring = CString::new(serialized).unwrap();
|
|
// Prevent optimization from eliminating the work
|
|
black_box(&cstring);
|
|
}
|
|
let serde_plus_cstring_ns = start_cstring.elapsed().as_nanos() as u64;
|
|
|
|
FfiOverheadResult {
|
|
pure_serde_ns,
|
|
serde_plus_cstring_ns,
|
|
iterations,
|
|
output_size,
|
|
}
|
|
}
|
|
|
|
/// Measures FFI overhead for CITM serialization.
|
|
#[no_mangle]
|
|
pub unsafe extern "C" fn measure_citm_ffi_overhead(
|
|
raw: *mut CitmCatalog,
|
|
iterations: u64
|
|
) -> FfiOverheadResult {
|
|
use std::time::Instant;
|
|
|
|
let catalog = &*raw;
|
|
let output_size: u64;
|
|
|
|
// Warm-up run
|
|
let warmup = serde_json::to_string(&catalog).unwrap();
|
|
output_size = warmup.len() as u64;
|
|
|
|
// Measure pure serde_json::to_string() - no CString conversion
|
|
let start_pure = Instant::now();
|
|
for _ in 0..iterations {
|
|
let serialized = serde_json::to_string(&catalog).unwrap();
|
|
black_box(&serialized);
|
|
}
|
|
let pure_serde_ns = start_pure.elapsed().as_nanos() as u64;
|
|
|
|
// Measure serde + CString conversion
|
|
let start_cstring = Instant::now();
|
|
for _ in 0..iterations {
|
|
let serialized = serde_json::to_string(&catalog).unwrap();
|
|
let cstring = CString::new(serialized).unwrap();
|
|
black_box(&cstring);
|
|
}
|
|
let serde_plus_cstring_ns = start_cstring.elapsed().as_nanos() as u64;
|
|
|
|
FfiOverheadResult {
|
|
pure_serde_ns,
|
|
serde_plus_cstring_ns,
|
|
iterations,
|
|
output_size,
|
|
}
|
|
}
|