Skip to content

Commit 29c68df

Browse files
committed
introduce iovec
Signed-off-by: qupeng <qupeng@pingcap.com>
1 parent b23f403 commit 29c68df

8 files changed

Lines changed: 647 additions & 185 deletions

File tree

Cargo.toml

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,11 +16,14 @@ fxhash = "0.2"
1616
nix = "0.18.0"
1717
crossbeam = "0.7"
1818
thiserror = "1.0"
19+
libc = "0.2"
1920

2021
[dev-dependencies]
2122
raft = { git = "https://github.com/tikv/raft-rs", branch = "master", default-features = false, features = ["protobuf-codec"] }
2223
tempfile = "3.1"
2324
toml = "0.5"
25+
ctor = "0.1"
26+
env_logger = "0.8"
2427

2528
[patch.crates-io]
26-
protobuf = { git = "https://github.com/pingcap/rust-protobuf", rev = "ec745253ab847481647887bc4c7cac3949449cfe" }
29+
protobuf = { git = "https://github.com/pingcap/rust-protobuf", rev = "ac10abf324a6f2b3e19e10f82b568a293ca5bd3d" }

src/compression.rs

Lines changed: 176 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,176 @@
1+
use std::i32;
2+
use std::ptr::{copy_nonoverlapping, read_unaligned};
3+
4+
use libc::c_int;
5+
use lz4_sys::{
6+
LZ4StreamEncode, LZ4_compressBound, LZ4_compress_default, LZ4_createStreamDecode,
7+
LZ4_decompress_safe, LZ4_decompress_safe_continue, LZ4_freeStreamDecode,
8+
};
9+
10+
// Layout of single block compression:
11+
// header + decoded_size + content + cap(tail).
12+
pub fn encode_block(src: &[u8], head_reserve: usize, tail_alloc: usize) -> Vec<u8> {
13+
unsafe {
14+
let bound = LZ4_compressBound(src.len() as i32);
15+
assert!(bound > 0 && src.len() <= i32::MAX as usize);
16+
17+
let capacity = head_reserve + 4 + bound as usize + tail_alloc;
18+
let mut output: Vec<u8> = Vec::with_capacity(capacity);
19+
20+
let le_len = src.len().to_le_bytes();
21+
copy_nonoverlapping(le_len.as_ptr(), output.as_mut_ptr().add(head_reserve), 4);
22+
23+
let size = LZ4_compress_default(
24+
src.as_ptr() as _,
25+
output.as_mut_ptr().add(head_reserve + 4) as _,
26+
src.len() as i32,
27+
bound,
28+
);
29+
assert!(size > 0);
30+
output.set_len(head_reserve + 4 + size as usize);
31+
output
32+
}
33+
}
34+
35+
pub fn decode_block(src: &[u8]) -> Vec<u8> {
36+
assert!(src.len() > 4, "data is too short: {} <= 4", src.len());
37+
unsafe {
38+
let len = u32::from_le(read_unaligned(src.as_ptr() as *const u32));
39+
let mut dst = Vec::with_capacity(len as usize);
40+
let l = LZ4_decompress_safe(
41+
src.as_ptr().add(4) as _,
42+
dst.as_mut_ptr() as _,
43+
src.len() as i32 - 4,
44+
dst.capacity() as i32,
45+
);
46+
assert_eq!(l, len as i32);
47+
dst.set_len(l as usize);
48+
dst
49+
}
50+
}
51+
52+
// Layout of multi blocks compression:
53+
// header + decoded_size + vec[encoded_len_and_content] + cap(tail).
54+
pub fn encode_blocks<'a, F, I>(inputs: F, head_reserve: usize, tail_alloc: usize) -> Vec<u8>
55+
where
56+
F: Fn() -> I,
57+
I: Iterator<Item = &'a [u8]>,
58+
{
59+
let (mut encoded_len, mut decoded_len) = (0, 0u64);
60+
for buffer in inputs() {
61+
let len = buffer.len();
62+
decoded_len += len as u64;
63+
let size = unsafe { lz4_sys::LZ4_compressBound(len as i32) };
64+
assert!(size > 0);
65+
encoded_len += (4 + size) as usize; // Length and content.
66+
}
67+
68+
let capacity = head_reserve + 8 + encoded_len + tail_alloc;
69+
let mut output: Vec<u8> = Vec::with_capacity(capacity);
70+
unsafe {
71+
copy_nonoverlapping(
72+
decoded_len.to_le_bytes().as_ptr(),
73+
output.as_mut_ptr().add(head_reserve),
74+
8,
75+
);
76+
77+
let (stream, mut offset) = (lz4_sys::LZ4_createStream(), head_reserve + 8);
78+
for buffer in inputs() {
79+
let bytes = LZ4_compress_fast_continue(
80+
stream,
81+
buffer.as_ptr() as _,
82+
output.as_mut_ptr().add(offset + 4),
83+
buffer.len() as i32,
84+
(capacity - offset) as i32,
85+
1, /* acceleration */
86+
);
87+
assert!(bytes > 0);
88+
copy_nonoverlapping(
89+
(bytes as u32).to_le_bytes().as_ptr(),
90+
output.as_mut_ptr().add(offset),
91+
4,
92+
);
93+
offset += (bytes + 4) as usize;
94+
}
95+
96+
lz4_sys::LZ4_freeStream(stream);
97+
output.set_len(offset);
98+
}
99+
output
100+
}
101+
102+
pub fn decode_blocks(mut src: &[u8]) -> Vec<u8> {
103+
assert!(src.len() > 8, "data is too short: {} <= 8", src.len());
104+
unsafe {
105+
let decoded_len = u64::from_le(read_unaligned(src.as_ptr() as *const u64));
106+
let mut dst: Vec<u8> = Vec::with_capacity(decoded_len as usize);
107+
src = &src[8..];
108+
109+
let (decoder, mut offset) = (LZ4_createStreamDecode(), 0);
110+
while !src.is_empty() {
111+
let len = u32::from_le(read_unaligned(src.as_ptr() as *const u32));
112+
let bytes = LZ4_decompress_safe_continue(
113+
decoder,
114+
src.as_ptr().add(4) as _,
115+
dst.as_mut_ptr().add(offset) as _,
116+
len as i32,
117+
(dst.capacity() - offset) as i32,
118+
);
119+
assert!(bytes >= 0);
120+
offset += bytes as usize;
121+
src = &src[(4 + len as usize)..];
122+
}
123+
LZ4_freeStreamDecode(decoder);
124+
assert_eq!(offset, decoded_len as usize);
125+
dst.set_len(offset);
126+
dst
127+
}
128+
}
129+
130+
extern "C" {
131+
// It's not in lz4_sys.
132+
fn LZ4_compress_fast_continue(
133+
LZ4_stream: *mut LZ4StreamEncode,
134+
source: *const u8,
135+
dest: *mut u8,
136+
input_size: c_int,
137+
dest_capacity: c_int,
138+
acceleration: c_int,
139+
) -> c_int;
140+
}
141+
142+
#[cfg(test)]
143+
mod tests {
144+
use super::*;
145+
146+
#[test]
147+
fn test_basic() {
148+
let data: Vec<&'static [u8]> = vec![b"", b"123", b"12345678910"];
149+
for d in data {
150+
let compressed = encode_block(d, 0, 0);
151+
assert!(compressed.len() > 4);
152+
let res = decode_block(&compressed);
153+
assert_eq!(res, d);
154+
}
155+
}
156+
157+
#[test]
158+
fn test_blocks() {
159+
let raw_inputs = vec![
160+
b"".to_vec(),
161+
b"123".to_vec(),
162+
b"12345678910".to_vec(),
163+
vec![b'x'; 99999],
164+
vec![0; 33333],
165+
];
166+
167+
let mut input = Vec::with_capacity(raw_inputs.iter().map(|x| x.len()).sum());
168+
for x in &raw_inputs {
169+
input.extend_from_slice(x);
170+
}
171+
172+
let encoded = encode_blocks(|| raw_inputs.iter().map(|x| x.as_slice()), 0, 0);
173+
let decoded = decode_blocks(&encoded);
174+
assert_eq!(input, decoded);
175+
}
176+
}

src/engine.rs

Lines changed: 11 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ use protobuf::Message;
99
use crate::cache_evict::{
1010
CacheSubmitor, CacheTask, Runner as CacheEvictRunner, DEFAULT_CACHE_CHUNK_SIZE,
1111
};
12+
use crate::compression::{decode_block, decode_blocks};
1213
use crate::config::{Config, RecoveryMode};
1314
use crate::log_batch::{
1415
self, Command, CompressionType, EntryExt, LogBatch, LogItemContent, OpType, CHECKSUM_LEN,
@@ -520,15 +521,19 @@ where
520521
let offset = base_offset + offset;
521522
pipe_log.fread(queue, file_num, offset, len)?
522523
}
523-
CompressionType::Lz4 => {
524-
let read_len = batch_len + HEADER_LEN as u64;
524+
c_type @ CompressionType::Lz4 | c_type @ CompressionType::Lz4Blocks => {
525+
let read_len = batch_len + HEADER_LEN as u64 + CHECKSUM_LEN as u64;
525526
let compressed = pipe_log.fread(queue, file_num, base_offset, read_len)?;
526-
let mut reader = compressed.as_ref();
527+
log_batch::test_batch_checksum(compressed.as_slice())?;
528+
529+
let mut reader = compressed.as_slice();
527530
let header = codec::decode_u64(&mut reader)?;
528531
assert_eq!(header >> 8, batch_len);
529-
530-
log_batch::test_batch_checksum(reader)?;
531-
let buf = log_batch::decompress(&reader[..batch_len as usize - CHECKSUM_LEN]);
532+
let buf = match c_type {
533+
CompressionType::Lz4 => decode_block(&reader[..batch_len as usize]),
534+
CompressionType::Lz4Blocks => decode_blocks(&reader[..batch_len as usize]),
535+
_ => unreachable!(),
536+
};
532537
let start = offset as usize - HEADER_LEN;
533538
let end = (offset + len) as usize - HEADER_LEN;
534539
buf[start..end].to_vec()

0 commit comments

Comments
 (0)