Skip to content

Commit

Permalink
Parallelize QuerySet::eval (#891)
Browse files Browse the repository at this point in the history
* Parallelize QuerySet::eval

* Reduce number of arguments for make_actor
  • Loading branch information
coolreader18 authored Feb 28, 2024
1 parent 6e088a7 commit 558822e
Show file tree
Hide file tree
Showing 10 changed files with 236 additions and 197 deletions.
100 changes: 19 additions & 81 deletions crates/core/src/host/host_controller.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,8 @@ use crate::energy::{EnergyMonitor, EnergyQuanta, NullEnergyMonitor};
use crate::hash::hash_bytes;
use crate::host;
use crate::messages::control_db::HostType;
use crate::module_host_context::ModuleHostContext;
use crate::module_host_context::{ModuleCreationContext, ModuleHostContext};
use crate::util::spawn_rayon;
use anyhow::Context;
use parking_lot::Mutex;
use serde::Serialize;
Expand All @@ -12,77 +13,13 @@ use std::sync::Arc;
use std::time::{Duration, Instant};

use super::module_host::{Catalog, EntityDef, EventStatus, ModuleHost, NoSuchModule, UpdateDatabaseResult};
use super::scheduler::SchedulerStarter;
use super::ReducerArgs;

pub struct HostController {
modules: Mutex<HashMap<u64, ModuleHost>>,
threadpool: Arc<HostThreadpool>,
pub energy_monitor: Arc<dyn EnergyMonitor>,
}

pub struct HostThreadpool {
inner: rayon_core::ThreadPool,
}

/// A Rayon [spawn_handler](https://docs.rs/rustc-rayon-core/latest/rayon_core/struct.ThreadPoolBuilder.html#method.spawn_handler)
/// which enters the given Tokio runtime at thread startup,
/// so that the Rayon workers can send along async channels.
///
/// Other than entering the `rt`, this spawn handler behaves identitically to the default Rayon spawn handler,
/// as documented in
/// https://docs.rs/rustc-rayon-core/0.5.0/rayon_core/struct.ThreadPoolBuilder.html#method.spawn_handler
///
/// Having Rayon threads block on async operations is a code smell.
/// We need to be careful that the Rayon threads never actually block,
/// i.e. that every async operation they invoke immediately completes.
/// I (pgoldman 2024-02-22) believe that our Rayon threads only ever send to unbounded channels,
/// and therefore never wait.
fn thread_spawn_handler(rt: tokio::runtime::Handle) -> impl FnMut(rayon::ThreadBuilder) -> Result<(), std::io::Error> {
move |thread| {
let rt = rt.clone();
let mut builder = std::thread::Builder::new();
if let Some(name) = thread.name() {
builder = builder.name(name.to_owned());
}
if let Some(stack_size) = thread.stack_size() {
builder = builder.stack_size(stack_size);
}
builder.spawn(move || {
let _rt_guard = rt.enter();
thread.run()
})?;
Ok(())
}
}

impl HostThreadpool {
fn new() -> Self {
let inner = rayon_core::ThreadPoolBuilder::new()
.thread_name(|_idx| "rayon-worker".to_string())
.spawn_handler(thread_spawn_handler(tokio::runtime::Handle::current()))
// TODO(perf, pgoldman 2024-02-22):
// in the case where we have many modules running many reducers,
// we'll wind up with Rayon threads competing with each other and with Tokio threads
// for CPU time.
//
// We should investigate creating two separate CPU pools,
// possibly via https://docs.rs/nix/latest/nix/sched/fn.sched_setaffinity.html,
// and restricting Tokio threads to one CPU pool
// and Rayon threads to the other.
// Then we should give Tokio and Rayon each a number of worker threads
// equal to the size of their pool.
.num_threads(std::thread::available_parallelism().unwrap().get())
.build()
.unwrap();
Self { inner }
}

pub fn spawn(&self, f: impl FnOnce() + Send + 'static) {
self.inner.spawn(f)
}
}

#[derive(PartialEq, Eq, Hash, Copy, Clone, Serialize, Debug)]
pub enum DescribedEntityType {
Table,
Expand Down Expand Up @@ -163,7 +100,6 @@ impl HostController {
pub fn new(energy_monitor: Arc<impl EnergyMonitor>) -> Self {
Self {
modules: Mutex::new(HashMap::new()),
threadpool: Arc::new(HostThreadpool::new()),
energy_monitor,
}
}
Expand Down Expand Up @@ -233,36 +169,38 @@ impl HostController {
/// impact the logic of applications. The idea being that if SpacetimeDB is a distributed operating
/// system, the applications will expect to be called when they are scheduled to be called regardless
/// of whether the OS has been restarted.
pub async fn spawn_module_host(&self, module_host_context: ModuleHostContext) -> Result<ModuleHost, anyhow::Error> {
let key = module_host_context.dbic.database_instance_id;

let (module_host, start_scheduler) = self.make_module_host(module_host_context)?;
pub async fn spawn_module_host(&self, mhc: ModuleHostContext) -> Result<ModuleHost, anyhow::Error> {
let key = mhc.dbic.database_instance_id;

let mcc = ModuleCreationContext {
dbic: mhc.dbic,
scheduler: mhc.scheduler,
program_hash: hash_bytes(&mhc.program_bytes),
program_bytes: mhc.program_bytes,
energy_monitor: self.energy_monitor.clone(),
};
let module_host = spawn_rayon(move || Self::make_module_host(mhc.host_type, mcc)).await?;

let old_module = self.modules.lock().insert(key, module_host.clone());
if let Some(old_module) = old_module {
old_module.exit().await
}
module_host.start();
start_scheduler.start(&module_host)?;
mhc.scheduler_starter.start(&module_host)?;

Ok(module_host)
}

fn make_module_host(&self, mhc: ModuleHostContext) -> anyhow::Result<(ModuleHost, SchedulerStarter)> {
let module_hash = hash_bytes(&mhc.program_bytes);
let (threadpool, energy_monitor) = (self.threadpool.clone(), self.energy_monitor.clone());
let module_host = match mhc.host_type {
fn make_module_host(host_type: HostType, mcc: ModuleCreationContext) -> anyhow::Result<ModuleHost> {
let module_host = match host_type {
HostType::Wasm => {
// make_actor with block_in_place since it's going to take some time to compute.
let start = Instant::now();
let actor = tokio::task::block_in_place(|| {
host::wasmtime::make_actor(mhc.dbic, module_hash, &mhc.program_bytes, mhc.scheduler, energy_monitor)
})?;
let actor = host::wasmtime::make_actor(mcc)?;
log::trace!("wasmtime::make_actor blocked for {:?}", start.elapsed());
ModuleHost::new(threadpool, actor)
ModuleHost::new(actor)
}
};
Ok((module_host, mhc.scheduler_starter))
Ok(module_host)
}

/// Determine if the module host described by [`ModuleHostContext`] is
Expand Down
28 changes: 11 additions & 17 deletions crates/core/src/host/module_host.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,7 @@ use std::time::Duration;
use base64::{engine::general_purpose::STANDARD as BASE_64_STD, Engine as _};
use futures::{Future, FutureExt};
use indexmap::IndexMap;
use tokio::sync::oneshot;

use super::host_controller::HostThreadpool;
use super::{ArgsTuple, InvalidReducerArguments, ReducerArgs, ReducerCallResult, ReducerId, Timestamp};
use crate::client::{ClientActorId, ClientConnectionSender};
use crate::database_logger::LogLevel;
Expand Down Expand Up @@ -315,7 +313,7 @@ impl fmt::Debug for ModuleHost {

#[async_trait::async_trait]
trait DynModuleHost: Send + Sync + 'static {
async fn get_instance(&self, db: Address) -> Result<(&HostThreadpool, Box<dyn ModuleInstance>), NoSuchModule>;
async fn get_instance(&self, db: Address) -> Result<Box<dyn ModuleInstance>, NoSuchModule>;
fn inject_logs(&self, log_level: LogLevel, message: &str);
fn one_off_query(
&self,
Expand All @@ -330,15 +328,14 @@ trait DynModuleHost: Send + Sync + 'static {

struct HostControllerActor<T: Module> {
module: Arc<T>,
threadpool: Arc<HostThreadpool>,
instance_pool: LendingPool<T::Instance>,
start: NotifyOnce,
}

impl<T: Module> HostControllerActor<T> {
fn spinup_new_instance(&self) {
let (module, instance_pool) = (self.module.clone(), self.instance_pool.clone());
self.threadpool.spawn(move || {
rayon::spawn(move || {
let instance = module.create_instance();
match instance_pool.add(instance) {
Ok(()) => {}
Expand All @@ -361,7 +358,7 @@ async fn select_first<A: Future, B: Future<Output = ()>>(fut_a: A, fut_b: B) ->

#[async_trait::async_trait]
impl<T: Module> DynModuleHost for HostControllerActor<T> {
async fn get_instance(&self, db: Address) -> Result<(&HostThreadpool, Box<dyn ModuleInstance>), NoSuchModule> {
async fn get_instance(&self, db: Address) -> Result<Box<dyn ModuleInstance>, NoSuchModule> {
self.start.notified().await;
// in the future we should do something like in the else branch here -- add more instances based on load.
// we need to do write-skew retries first - right now there's only ever once instance per module.
Expand All @@ -379,11 +376,10 @@ impl<T: Module> DynModuleHost for HostControllerActor<T> {
.await
.map_err(|_| NoSuchModule)?
};
let inst = AutoReplacingModuleInstance {
Ok(Box::new(AutoReplacingModuleInstance {
inst,
module: self.module.clone(),
};
Ok((&self.threadpool, Box::new(inst)))
}))
}

fn inject_logs(&self, log_level: LogLevel, message: &str) {
Expand Down Expand Up @@ -459,13 +455,12 @@ pub enum InitDatabaseError {
}

impl ModuleHost {
pub fn new(threadpool: Arc<HostThreadpool>, mut module: impl Module) -> Self {
pub fn new(mut module: impl Module) -> Self {
let info = module.info();
let instance_pool = LendingPool::new();
instance_pool.add_multiple(module.initial_instances()).unwrap();
let inner = Arc::new(HostControllerActor {
module: Arc::new(module),
threadpool,
instance_pool,
start: NotifyOnce::new(),
});
Expand All @@ -491,13 +486,12 @@ impl ModuleHost {
F: FnOnce(&mut dyn ModuleInstance) -> R + Send + 'static,
R: Send + 'static,
{
let (threadpool, mut inst) = self.inner.get_instance(self.info.address).await?;
let mut inst = self.inner.get_instance(self.info.address).await?;

let (tx, rx) = oneshot::channel();
threadpool.spawn(move || {
let _ = tx.send(f(&mut *inst));
});
Ok(rx.await.expect("instance panicked"))
let result = tokio::task::spawn_blocking(move || f(&mut *inst))
.await
.unwrap_or_else(|e| std::panic::resume_unwind(e.into_panic()));
Ok(result)
}

pub async fn disconnect_client(&self, client_id: ClientActorId) {
Expand Down
23 changes: 13 additions & 10 deletions crates/core/src/host/wasm_common/module_host_actor.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,6 @@ use crate::db::datastore::locking_tx_datastore::MutTxId;
use crate::db::datastore::traits::IsolationLevel;
use crate::energy::{EnergyMonitor, EnergyQuanta, ReducerBudget, ReducerFingerprint};
use crate::execution_context::ExecutionContext;
use crate::hash::Hash;
use crate::host::instance_env::InstanceEnv;
use crate::host::module_host::{
CallReducerParams, DatabaseUpdate, EventStatus, Module, ModuleEvent, ModuleFunctionCall, ModuleInfo,
Expand All @@ -24,6 +23,7 @@ use crate::host::module_host::{
use crate::host::{ArgsTuple, EntityDef, ReducerCallResult, ReducerId, ReducerOutcome, Scheduler, Timestamp};
use crate::identity::Identity;
use crate::messages::control_db::Database;
use crate::module_host_context::ModuleCreationContext;
use crate::sql;
use crate::subscription::module_subscription_actor::ModuleSubscriptions;
use crate::util::const_unwrap;
Expand Down Expand Up @@ -53,7 +53,7 @@ pub trait WasmInstance: Send + Sync + 'static {

fn instance_env(&self) -> &InstanceEnv;

type Trap;
type Trap: Send;

fn call_reducer(&mut self, op: ReducerOp<'_>, budget: ReducerBudget) -> ExecuteResult<Self::Trap>;

Expand Down Expand Up @@ -123,13 +123,14 @@ pub enum DescribeError {
}

impl<T: WasmModule> WasmModuleHostActor<T> {
pub fn new(
database_instance_context: Arc<DatabaseInstanceContext>,
module_hash: Hash,
module: T,
scheduler: Scheduler,
energy_monitor: Arc<dyn EnergyMonitor>,
) -> Result<Self, InitializationError> {
pub fn new(mcc: ModuleCreationContext, module: T) -> Result<Self, InitializationError> {
let ModuleCreationContext {
dbic: database_instance_context,
scheduler,
program_bytes: _,
program_hash: module_hash,
energy_monitor,
} = mcc;
log::trace!(
"Making new module host actor for database {}",
database_instance_context.address
Expand Down Expand Up @@ -533,7 +534,9 @@ impl<T: WasmInstance> WasmModuleInstance<T> {
)
.entered();

let (tx, result) = tx_slot.set(tx, || self.instance.call_reducer(op, budget));
// run the call_reducer call in rayon. it's important that we don't acquire a lock inside a rayon task,
// as that can lead to deadlock.
let (tx, result) = rayon::scope(|_| tx_slot.set(tx, || self.instance.call_reducer(op, budget)));

let ExecuteResult {
energy,
Expand Down
19 changes: 5 additions & 14 deletions crates/core/src/host/wasmtime/mod.rs
Original file line number Diff line number Diff line change
@@ -1,14 +1,12 @@
use std::borrow::Cow;
use std::sync::Arc;

use anyhow::Context;
use once_cell::sync::Lazy;
use wasmtime::{Engine, Linker, Module, StoreContext, StoreContextMut};

use crate::database_instance_context::DatabaseInstanceContext;
use crate::energy::{EnergyMonitor, EnergyQuanta, ReducerBudget};
use crate::energy::{EnergyQuanta, ReducerBudget};
use crate::error::NodesError;
use crate::hash::Hash;
use crate::module_host_context::ModuleCreationContext;
use crate::stdb_path;

mod wasm_instance_env;
Expand All @@ -20,7 +18,6 @@ use self::wasm_instance_env::WasmInstanceEnv;

use super::wasm_common::module_host_actor::InitializationError;
use super::wasm_common::{abi, module_host_actor::WasmModuleHostActor, ModuleCreationError};
use super::Scheduler;

static ENGINE: Lazy<Engine> = Lazy::new(|| {
let mut config = wasmtime::Config::new();
Expand Down Expand Up @@ -55,14 +52,8 @@ static LINKER: Lazy<Linker<WasmInstanceEnv>> = Lazy::new(|| {
linker
});

pub fn make_actor(
dbic: Arc<DatabaseInstanceContext>,
module_hash: Hash,
program_bytes: &[u8],
scheduler: Scheduler,
energy_monitor: Arc<dyn EnergyMonitor>,
) -> Result<impl super::module_host::Module, ModuleCreationError> {
let module = Module::new(&ENGINE, program_bytes).map_err(ModuleCreationError::WasmCompileError)?;
pub fn make_actor(mcc: ModuleCreationContext) -> Result<impl super::module_host::Module, ModuleCreationError> {
let module = Module::new(&ENGINE, &mcc.program_bytes).map_err(ModuleCreationError::WasmCompileError)?;

let func_imports = module
.imports()
Expand All @@ -77,7 +68,7 @@ pub fn make_actor(

let module = WasmtimeModule::new(module);

WasmModuleHostActor::new(dbic, module_hash, module, scheduler, energy_monitor).map_err(Into::into)
WasmModuleHostActor::new(mcc, module).map_err(Into::into)
}

#[derive(Debug, derive_more::From)]
Expand Down
11 changes: 11 additions & 0 deletions crates/core/src/module_host_context.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
use spacetimedb_lib::Hash;

use crate::database_instance_context::DatabaseInstanceContext;
use crate::energy::EnergyMonitor;
use crate::host::scheduler::{Scheduler, SchedulerStarter};
use crate::messages::control_db::HostType;
use crate::util::AnyBytes;
Expand All @@ -11,3 +14,11 @@ pub struct ModuleHostContext {
pub host_type: HostType,
pub program_bytes: AnyBytes,
}

pub struct ModuleCreationContext {
pub dbic: Arc<DatabaseInstanceContext>,
pub scheduler: Scheduler,
pub program_bytes: AnyBytes,
pub program_hash: Hash,
pub energy_monitor: Arc<dyn EnergyMonitor>,
}
Loading

2 comments on commit 558822e

@github-actions
Copy link

@github-actions github-actions bot commented on 558822e Feb 28, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Benchmark results

Benchmark Report

Legend:

  • load: number of rows pre-loaded into the database
  • count: number of rows touched by the transaction
  • index types:
    • unique: a single index on the id column
    • non_unique: no indexes
    • multi_index: non-unique index on every column
  • schemas:
    • person(id: u32, name: String, age: u64)
    • location(id: u32, x: u64, y: u64)

All throughputs are single-threaded.

Empty transaction

db on disk new latency old latency new throughput old throughput
sqlite 💿 422.5±1.09ns 422.4±1.52ns - -
sqlite 🧠 416.5±3.11ns 411.9±1.22ns - -
stdb_raw 💿 776.3±0.88ns 770.4±1.50ns - -
stdb_raw 🧠 723.6±1.21ns 719.7±1.51ns - -

Single-row insertions

db on disk schema index type load new latency old latency new throughput old throughput

Multi-row insertions

db on disk schema index type load count new latency old latency new throughput old throughput
sqlite 💿 u32_u64_str btree_each_column 2048 256 518.6±0.45µs 521.0±0.56µs 1928 tx/sec 1919 tx/sec
sqlite 💿 u32_u64_str unique_0 2048 256 136.4±0.44µs 137.5±0.70µs 7.2 Ktx/sec 7.1 Ktx/sec
sqlite 💿 u32_u64_u64 btree_each_column 2048 256 420.8±0.75µs 422.5±0.58µs 2.3 Ktx/sec 2.3 Ktx/sec
sqlite 💿 u32_u64_u64 unique_0 2048 256 125.9±0.41µs 126.2±0.31µs 7.8 Ktx/sec 7.7 Ktx/sec
sqlite 🧠 u32_u64_str btree_each_column 2048 256 449.2±1.17µs 451.4±0.49µs 2.2 Ktx/sec 2.2 Ktx/sec
sqlite 🧠 u32_u64_str unique_0 2048 256 124.0±0.51µs 120.1±0.31µs 7.9 Ktx/sec 8.1 Ktx/sec
sqlite 🧠 u32_u64_u64 btree_each_column 2048 256 364.8±0.43µs 368.6±0.50µs 2.7 Ktx/sec 2.6 Ktx/sec
sqlite 🧠 u32_u64_u64 unique_0 2048 256 108.2±0.92µs 106.8±0.49µs 9.0 Ktx/sec 9.1 Ktx/sec
stdb_raw 💿 u32_u64_str btree_each_column 2048 256 742.0±0.69µs 743.4±0.77µs 1347 tx/sec 1345 tx/sec
stdb_raw 💿 u32_u64_str unique_0 2048 256 640.9±0.68µs 641.2±1.18µs 1560 tx/sec 1559 tx/sec
stdb_raw 💿 u32_u64_u64 btree_each_column 2048 256 452.1±0.48µs 454.1±0.51µs 2.2 Ktx/sec 2.2 Ktx/sec
stdb_raw 💿 u32_u64_u64 unique_0 2048 256 404.8±0.33µs 404.3±0.27µs 2.4 Ktx/sec 2.4 Ktx/sec
stdb_raw 🧠 u32_u64_str btree_each_column 2048 256 517.9±0.40µs 517.3±0.25µs 1930 tx/sec 1933 tx/sec
stdb_raw 🧠 u32_u64_str unique_0 2048 256 422.8±0.44µs 421.9±0.59µs 2.3 Ktx/sec 2.3 Ktx/sec
stdb_raw 🧠 u32_u64_u64 btree_each_column 2048 256 349.3±0.87µs 347.7±0.57µs 2.8 Ktx/sec 2.8 Ktx/sec
stdb_raw 🧠 u32_u64_u64 unique_0 2048 256 305.7±0.69µs 305.4±0.17µs 3.2 Ktx/sec 3.2 Ktx/sec

Full table iterate

db on disk schema index type new latency old latency new throughput old throughput
sqlite 💿 u32_u64_str unique_0 21.7±0.15µs 20.5±0.12µs 44.9 Ktx/sec 47.6 Ktx/sec
sqlite 💿 u32_u64_u64 unique_0 19.6±0.12µs 19.3±0.28µs 49.9 Ktx/sec 50.6 Ktx/sec
sqlite 🧠 u32_u64_str unique_0 20.4±0.30µs 19.2±0.12µs 47.8 Ktx/sec 50.8 Ktx/sec
sqlite 🧠 u32_u64_u64 unique_0 18.3±0.04µs 17.8±0.13µs 53.3 Ktx/sec 54.8 Ktx/sec
stdb_raw 💿 u32_u64_str unique_0 17.8±0.00µs 17.8±0.00µs 54.9 Ktx/sec 54.9 Ktx/sec
stdb_raw 💿 u32_u64_u64 unique_0 14.9±0.00µs 15.0±0.00µs 65.4 Ktx/sec 65.3 Ktx/sec
stdb_raw 🧠 u32_u64_str unique_0 17.7±0.00µs 17.7±0.00µs 55.1 Ktx/sec 55.1 Ktx/sec
stdb_raw 🧠 u32_u64_u64 unique_0 14.9±0.00µs 14.9±0.00µs 65.6 Ktx/sec 65.6 Ktx/sec

Find unique key

db on disk key type load new latency old latency new throughput old throughput

Filter

db on disk key type index strategy load count new latency old latency new throughput old throughput
sqlite 💿 string index 2048 256 65.0±0.14µs 65.6±0.27µs 15.0 Ktx/sec 14.9 Ktx/sec
sqlite 💿 u64 index 2048 256 61.7±0.16µs 61.8±0.16µs 15.8 Ktx/sec 15.8 Ktx/sec
sqlite 🧠 string index 2048 256 62.9±0.13µs 63.5±0.29µs 15.5 Ktx/sec 15.4 Ktx/sec
sqlite 🧠 u64 index 2048 256 56.9±0.07µs 58.3±0.22µs 17.2 Ktx/sec 16.8 Ktx/sec
stdb_raw 💿 string index 2048 256 5.7±0.00µs 5.7±0.00µs 170.6 Ktx/sec 172.4 Ktx/sec
stdb_raw 💿 u64 index 2048 256 5.6±0.00µs 5.6±0.00µs 175.1 Ktx/sec 175.1 Ktx/sec
stdb_raw 🧠 string index 2048 256 5.7±0.00µs 5.6±0.00µs 172.7 Ktx/sec 174.7 Ktx/sec
stdb_raw 🧠 u64 index 2048 256 5.5±0.00µs 5.5±0.00µs 177.1 Ktx/sec 176.9 Ktx/sec

Serialize

schema format count new latency old latency new throughput old throughput
u32_u64_str bsatn 100 2.5±0.00µs 3.0±0.00µs 38.5 Mtx/sec 31.8 Mtx/sec
u32_u64_str json 100 4.9±0.01µs 5.2±0.05µs 19.5 Mtx/sec 18.4 Mtx/sec
u32_u64_str product_value 100 675.1±0.74ns 673.8±0.65ns 141.3 Mtx/sec 141.5 Mtx/sec
u32_u64_u64 bsatn 100 1768.5±51.87ns 1705.0±46.41ns 53.9 Mtx/sec 55.9 Mtx/sec
u32_u64_u64 json 100 3.2±0.03µs 3.2±0.16µs 29.6 Mtx/sec 29.5 Mtx/sec
u32_u64_u64 product_value 100 598.8±0.31ns 562.0±1.13ns 159.3 Mtx/sec 169.7 Mtx/sec

Module: invoke with large arguments

arg size new latency old latency new throughput old throughput
64KiB 84.2±13.45µs 60.1±5.49µs - -

Module: print bulk

line count new latency old latency new throughput old throughput
1 33.5±2.46µs 26.6±2.61µs - -
100 211.4±1.53µs 194.0±2.88µs - -
1000 1817.6±18.13µs 1782.6±14.58µs - -

Remaining benchmarks

name new latency old latency new throughput old throughput
sqlite/💿/update_bulk/u32_u64_str/unique_0/load=2048/count=256 48.4±0.27µs 46.0±0.29µs 20.2 Ktx/sec 21.2 Ktx/sec
sqlite/💿/update_bulk/u32_u64_u64/unique_0/load=2048/count=256 41.2±0.27µs 40.7±0.18µs 23.7 Ktx/sec 24.0 Ktx/sec
sqlite/🧠/update_bulk/u32_u64_str/unique_0/load=2048/count=256 41.1±0.15µs 39.9±0.29µs 23.8 Ktx/sec 24.5 Ktx/sec
sqlite/🧠/update_bulk/u32_u64_u64/unique_0/load=2048/count=256 36.3±0.20µs 34.6±0.09µs 26.9 Ktx/sec 28.2 Ktx/sec
stdb_module/💿/update_bulk/u32_u64_str/unique_0/load=2048/count=256 3.2±0.01ms 2.1±0.27ms 309 tx/sec 473 tx/sec
stdb_module/💿/update_bulk/u32_u64_u64/unique_0/load=2048/count=256 2.3±0.00ms 1431.3±2.25µs 428 tx/sec 698 tx/sec
stdb_raw/💿/update_bulk/u32_u64_str/unique_0/load=2048/count=256 1149.7±1.78µs 1158.9±2.12µs 869 tx/sec 862 tx/sec
stdb_raw/💿/update_bulk/u32_u64_u64/unique_0/load=2048/count=256 795.5±0.55µs 793.0±0.56µs 1257 tx/sec 1261 tx/sec
stdb_raw/🧠/update_bulk/u32_u64_str/unique_0/load=2048/count=256 823.9±0.88µs 828.6±0.55µs 1213 tx/sec 1206 tx/sec
stdb_raw/🧠/update_bulk/u32_u64_u64/unique_0/load=2048/count=256 599.9±0.62µs 593.8±0.84µs 1667 tx/sec 1684 tx/sec

@github-actions
Copy link

@github-actions github-actions bot commented on 558822e Feb 28, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Benchmark results <title>502 Bad Gateway</title>

502 Bad Gateway


nginx

Please sign in to comment.