atagen

matrix-rust-sdk

public
Star0
Watch0
ssh://brewery@pls.lobotomise.me:33/atagen/matrix-rust-sdk.git
benchmarks/benches/linked_chunk.rsRust265 lines9.6 KB
1
use std::{sync::Arc, time::Duration};
2
3
use criterion::{BatchSize, BenchmarkId, Criterion, Throughput, criterion_group, criterion_main};
4
use matrix_sdk::{
5
    SqliteEventCacheStore,
6
    linked_chunk::{LinkedChunk, LinkedChunkId, Update, lazy_loader},
7
};
8
use matrix_sdk_base::event_cache::{
9
    Event, Gap,
10
    store::{DEFAULT_CHUNK_CAPACITY, DynEventCacheStore, IntoEventCacheStore, MemoryStore},
11
};
12
use matrix_sdk_test::{ALICE, event_factory::EventFactory};
13
use ruma::room_id;
14
use tempfile::tempdir;
15
use tokio::runtime::Builder;
16
17
#[derive(Clone, Debug)]
18
enum Operation {
19
    PushItemsBack(Vec<Event>),
20
    PushGapBack(Gap),
21
}
22
23
#[cfg(not(feature = "codspeed"))]
24
const NUMBER_OF_EVENTS: &[u64] = &[10, 100, 1000, 10_000, 100_000];
25
#[cfg(feature = "codspeed")]
26
const NUMBER_OF_EVENTS: &[u64] = &[10, 100, 1000];
27
28
fn writing(c: &mut Criterion) {
29
    // Create a new asynchronous runtime.
30
    let runtime = Builder::new_multi_thread()
31
        .enable_time()
32
        .enable_io()
33
        .build()
34
        .expect("Failed to create an asynchronous runtime");
35
36
    let room_id = room_id!("!fabricandofitfaber:bar.baz");
37
    let linked_chunk_id = LinkedChunkId::Room(room_id);
38
    let event_factory = EventFactory::new().room(room_id).sender(&ALICE);
39
40
    let mut group = c.benchmark_group("Linked chunk writing");
41
    group.sample_size(10).measurement_time(Duration::from_secs(30));
42
43
    for &number_of_events in NUMBER_OF_EVENTS {
44
        let sqlite_temp_dir = tempdir().unwrap();
45
46
        // Declare new stores for this set of events.
47
        let stores: [(&str, Option<Arc<DynEventCacheStore>>); 3] = [
48
            ("none", None),
49
            ("memory store", Some(MemoryStore::default().into_event_cache_store())),
50
            (
51
                "sqlite store",
52
                runtime.block_on(async {
53
                    Some(
54
                        SqliteEventCacheStore::open(sqlite_temp_dir.path().join("bench"), None)
55
                            .await
56
                            .unwrap()
57
                            .into_event_cache_store(),
58
                    )
59
                }),
60
            ),
61
        ];
62
63
        for (store_name, store) in stores {
64
            // Create the operations we want to bench.
65
            let mut operations = Vec::new();
66
67
            {
68
                let mut events = (0..number_of_events)
69
                    .map(|nth| event_factory.text_msg(format!("foo {nth}")).into_event())
70
                    .peekable();
71
72
                let mut gap_nth = 0;
73
74
                while events.peek().is_some() {
75
                    {
76
                        let events_to_push_back = events.by_ref().take(80).collect::<Vec<_>>();
77
78
                        if events_to_push_back.is_empty() {
79
                            break;
80
                        }
81
82
                        operations.push(Operation::PushItemsBack(events_to_push_back));
83
                    }
84
85
                    {
86
                        operations.push(Operation::PushGapBack(Gap {
87
                            token: format!("gap{gap_nth}"),
88
                            history: None,
89
                        }));
90
                        gap_nth += 1;
91
                    }
92
                }
93
            }
94
95
            // Define the throughput.
96
            group.throughput(Throughput::Elements(number_of_events));
97
98
            // Get a bencher.
99
            group.bench_with_input(
100
                BenchmarkId::new(format!("Linked chunk writing [{store_name}]"), number_of_events),
101
                &operations,
102
                |bencher, operations| {
103
                    // Bench the routine.
104
                    bencher.to_async(&runtime).iter_batched(
105
                        || operations.clone(),
106
                        |operations| async {
107
                            // The routine to bench!
108
109
                            let mut linked_chunk = LinkedChunk::<DEFAULT_CHUNK_CAPACITY, Event, Gap>::new_with_update_history();
110
111
                            for operation in operations {
112
                                match operation {
113
                                    Operation::PushItemsBack(events) => linked_chunk.push_items_back(events),
114
                                    Operation::PushGapBack(gap) => linked_chunk.push_gap_back(gap),
115
                                }
116
                            }
117
118
                            if let Some(store) = &store {
119
                                let updates = linked_chunk.updates().unwrap().take();
120
                                store.handle_linked_chunk_updates(linked_chunk_id, updates).await.unwrap();
121
                                // Empty the store.
122
                                store.handle_linked_chunk_updates(linked_chunk_id, vec![Update::Clear]).await.unwrap();
123
                            }
124
125
                        },
126
                        BatchSize::SmallInput
127
                    )
128
                },
129
            );
130
131
            {
132
                let _guard = runtime.enter();
133
                drop(store);
134
            }
135
        }
136
    }
137
138
    group.finish()
139
}
140
141
fn reading(c: &mut Criterion) {
142
    // Create a new asynchronous runtime.
143
    let runtime = Builder::new_multi_thread()
144
        .enable_time()
145
        .enable_io()
146
        .build()
147
        .expect("Failed to create an asynchronous runtime");
148
149
    let room_id = room_id!("!fabricandofitfaber:bar.baz");
150
    let linked_chunk_id = LinkedChunkId::Room(room_id);
151
    let event_factory = EventFactory::new().room(room_id).sender(&ALICE);
152
153
    let mut group = c.benchmark_group("Linked chunk reading");
154
    group.sample_size(10);
155
156
    for &num_events in NUMBER_OF_EVENTS {
157
        let sqlite_temp_dir = tempdir().unwrap();
158
159
        // Declare new stores for this set of events.
160
        let stores: [(&str, Arc<DynEventCacheStore>); 2] = [
161
            ("memory store", MemoryStore::default().into_event_cache_store()),
162
            (
163
                "sqlite store",
164
                runtime.block_on(async {
165
                    SqliteEventCacheStore::open(sqlite_temp_dir.path().join("bench"), None)
166
                        .await
167
                        .unwrap()
168
                        .into_event_cache_store()
169
                }),
170
            ),
171
        ];
172
173
        for (store_name, store) in stores {
174
            // Store some events and gap chunks in the store.
175
            {
176
                let mut events = (0..num_events)
177
                    .map(|nth| event_factory.text_msg(format!("foo {nth}")).into_event())
178
                    .peekable();
179
180
                let mut lc =
181
                    LinkedChunk::<DEFAULT_CHUNK_CAPACITY, Event, Gap>::new_with_update_history();
182
                let mut num_gaps = 0;
183
184
                while events.peek().is_some() {
185
                    let events_chunk = events.by_ref().take(80).collect::<Vec<_>>();
186
187
                    if events_chunk.is_empty() {
188
                        break;
189
                    }
190
191
                    lc.push_items_back(events_chunk);
192
                    lc.push_gap_back(Gap { token: format!("gap{num_gaps}"), history: None });
193
194
                    num_gaps += 1;
195
                }
196
197
                // Now persist the updates to recreate this full linked chunk.
198
                let updates = lc.updates().unwrap().take();
199
                runtime
200
                    .block_on(store.handle_linked_chunk_updates(linked_chunk_id, updates))
201
                    .unwrap();
202
            }
203
204
            // Define the throughput.
205
            group.throughput(Throughput::Elements(num_events));
206
207
            // Bench the lazy loader.
208
            group.bench_function(
209
                BenchmarkId::new(format!("Linked chunk lazy loader[{store_name}]"), num_events),
210
                |bencher| {
211
                    // Bench the routine.
212
                    bencher.to_async(&runtime).iter(|| async {
213
                        // Load the last chunk first,
214
                        let (last_chunk, chunk_id_gen) =
215
                            store.load_last_chunk(linked_chunk_id).await.unwrap();
216
217
                        let mut lc =
218
                            lazy_loader::from_last_chunk::<128, _, _>(last_chunk, chunk_id_gen)
219
                                .expect("no error when reconstructing the linked chunk")
220
                                .expect("there is a linked chunk in the store");
221
222
                        // Then load until the start of the linked chunk.
223
                        let mut cur_chunk_id = lc.chunks().next().unwrap().identifier();
224
                        while let Some(prev) =
225
                            store.load_previous_chunk(linked_chunk_id, cur_chunk_id).await.unwrap()
226
                        {
227
                            cur_chunk_id = prev.identifier;
228
                            lazy_loader::insert_new_first_chunk(&mut lc, prev)
229
                                .expect("no error when linking the previous lazy-loaded chunk");
230
                        }
231
                    })
232
                },
233
            );
234
235
            // Bench the metadata loader.
236
            group.bench_function(
237
                BenchmarkId::new(format!("Linked chunk metadata loader[{store_name}]"), num_events),
238
                |bencher| {
239
                    // Bench the routine.
240
                    bencher.to_async(&runtime).iter(|| async {
241
                        let _metadata = store
242
                            .load_all_chunks_metadata(linked_chunk_id)
243
                            .await
244
                            .expect("metadata must load");
245
                    })
246
                },
247
            );
248
249
            {
250
                let _guard = runtime.enter();
251
                drop(store);
252
            }
253
        }
254
    }
255
256
    group.finish()
257
}
258
259
criterion_group! {
260
    name = event_cache;
261
    config = Criterion::default();
262
    targets = writing, reading,
263
}
264
265
criterion_main!(event_cache);