Skip to content

Commit 2d4bc11

Browse files
authored
Remove a superfluous copy during write serialization (#1087)
Right now, we copy data from a `BlockOp::Write/WriteUnwritten` into a `BytesMut` buffer, encrypt it in-place, then serialize it using `CrucibleEncoder`. Serialization requires a *second* full copy of the data. This is noticeable in our flamegraphs for large writes; see the PR for images. This change removes that `memcpy`, speeding up large writes by a noticeable amount; when the PR was first opened, I saw a ~30% speedup for 1M and 4M random writes.
1 parent 1b0e3be commit 2d4bc11

File tree

12 files changed

+614
-270
lines changed

12 files changed

+614
-270
lines changed

Cargo.lock

+2
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

+1
Original file line numberDiff line numberDiff line change
@@ -90,6 +90,7 @@ slog-term = { version = "2.9" }
9090
static_assertions = "1.1.0"
9191
statistical = "1.0.0"
9292
subprocess = "0.2.9"
93+
strum_macros = "0.25.3"
9394
tempfile = "3"
9495
test-strategy = "0.3.1"
9596
thiserror = "1"

downstairs/src/lib.rs

+69
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,75 @@ fn deadline_secs(secs: u64) -> Instant {
5353
.unwrap()
5454
}
5555

56+
#[allow(clippy::derive_partial_eq_without_eq)]
57+
#[derive(Debug, Clone, PartialEq)]
58+
enum IOop {
59+
Write {
60+
dependencies: Vec<JobId>, // Jobs that must finish before this
61+
writes: Vec<crucible_protocol::Write>,
62+
},
63+
WriteUnwritten {
64+
dependencies: Vec<JobId>, // Jobs that must finish before this
65+
writes: Vec<crucible_protocol::Write>,
66+
},
67+
Read {
68+
dependencies: Vec<JobId>, // Jobs that must finish before this
69+
requests: Vec<ReadRequest>,
70+
},
71+
Flush {
72+
dependencies: Vec<JobId>, // Jobs that must finish before this
73+
flush_number: u64,
74+
gen_number: u64,
75+
snapshot_details: Option<SnapshotDetails>,
76+
extent_limit: Option<usize>,
77+
},
78+
/*
79+
* These operations are for repairing a bad downstairs
80+
*/
81+
ExtentClose {
82+
dependencies: Vec<JobId>, // Jobs that must finish before this
83+
extent: usize,
84+
},
85+
ExtentFlushClose {
86+
dependencies: Vec<JobId>, // Jobs that must finish before this
87+
extent: usize,
88+
flush_number: u64,
89+
gen_number: u64,
90+
source_downstairs: ClientId,
91+
repair_downstairs: Vec<ClientId>,
92+
},
93+
ExtentLiveRepair {
94+
dependencies: Vec<JobId>, // Jobs that must finish before this
95+
extent: usize,
96+
source_downstairs: ClientId,
97+
source_repair_address: SocketAddr,
98+
repair_downstairs: Vec<ClientId>,
99+
},
100+
ExtentLiveReopen {
101+
dependencies: Vec<JobId>, // Jobs that must finish before this
102+
extent: usize,
103+
},
104+
ExtentLiveNoOp {
105+
dependencies: Vec<JobId>, // Jobs that must finish before this
106+
},
107+
}
108+
109+
impl IOop {
110+
fn deps(&self) -> &[JobId] {
111+
match &self {
112+
IOop::Write { dependencies, .. }
113+
| IOop::Flush { dependencies, .. }
114+
| IOop::Read { dependencies, .. }
115+
| IOop::WriteUnwritten { dependencies, .. }
116+
| IOop::ExtentClose { dependencies, .. }
117+
| IOop::ExtentFlushClose { dependencies, .. }
118+
| IOop::ExtentLiveRepair { dependencies, .. }
119+
| IOop::ExtentLiveReopen { dependencies, .. }
120+
| IOop::ExtentLiveNoOp { dependencies } => dependencies,
121+
}
122+
}
123+
}
124+
56125
/*
57126
* Export the contents or partial contents of a Downstairs Region to
58127
* the file indicated.

protocol/Cargo.toml

+1
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ crucible-common.workspace = true
1313
num_enum.workspace = true
1414
schemars.workspace = true
1515
serde.workspace = true
16+
strum_macros.workspace = true
1617
tokio-util.workspace = true
1718
uuid.workspace = true
1819
crucible-workspace-hack.workspace = true

protocol/src/lib.rs

+9-1
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ use anyhow::bail;
66
use bytes::{Buf, BufMut, BytesMut};
77
use num_enum::IntoPrimitive;
88
use serde::{Deserialize, Serialize};
9+
use strum_macros::EnumDiscriminants;
910
use tokio_util::codec::{Decoder, Encoder};
1011
use uuid::Uuid;
1112

@@ -282,7 +283,10 @@ pub const CRUCIBLE_MESSAGE_VERSION: u32 = 5;
282283
* go do that right now before you forget.
283284
*/
284285
#[allow(clippy::derive_partial_eq_without_eq)]
285-
#[derive(Debug, PartialEq, Clone, Serialize, Deserialize)]
286+
#[derive(
287+
Debug, PartialEq, Clone, Serialize, Deserialize, EnumDiscriminants,
288+
)]
289+
#[strum_discriminants(derive(Serialize))]
286290
#[repr(u16)]
287291
pub enum Message {
288292
/**
@@ -531,6 +535,8 @@ pub enum Message {
531535
/*
532536
* IO related
533537
*/
538+
// Message::Write must contain the same fields in the same order as
539+
// RawMessage::Write which is used for zero-copy serialization.
534540
Write {
535541
upstairs_id: Uuid,
536542
session_id: Uuid,
@@ -580,6 +586,8 @@ pub enum Message {
580586
responses: Result<Vec<ReadResponse>, CrucibleError>,
581587
},
582588

589+
// Message::WriteUnwritten must contain the same fields in the same order as
590+
// RawMessage::WriteUnwritten, which is used for zero-copy serialization.
583591
WriteUnwritten {
584592
upstairs_id: Uuid,
585593
session_id: Uuid,

upstairs/Cargo.toml

+1
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ anyhow.workspace = true
1717
async-trait.workspace = true
1818
async-recursion.workspace = true
1919
base64.workspace = true
20+
bincode.workspace = true
2021
bytes.workspace = true
2122
chrono.workspace = true
2223
crucible-common.workspace = true

0 commit comments

Comments
 (0)