Skip to main content

webgraph_cli/bench/
bvgraph.rs

1/*
2 * SPDX-FileCopyrightText: 2023 Inria
3 * SPDX-FileCopyrightText: 2023 Tommaso Fontana
4 *
5 * SPDX-License-Identifier: Apache-2.0 OR LGPL-2.1-or-later
6 */
7
8use crate::GlobalArgs;
9use anyhow::Result;
10use clap::Parser;
11use dsi_bitstream::dispatch::factory::CodesReaderFactoryHelper;
12use dsi_bitstream::prelude::*;
13use itertools::Itertools;
14use lender::*;
15use rand::RngExt;
16use rand::SeedableRng;
17use rand::rngs::SmallRng;
18use std::hint::black_box;
19use std::path::PathBuf;
20use webgraph::prelude::*;
21
22#[derive(Parser, Debug)]
23#[command(name = "bvgraph", about = "Benchmarks the Rust BvGraph implementation.", long_about = None)]
24pub struct CliArgs {
25    /// The basename of the graph.
26    pub basename: PathBuf,
27
28    /// Perform a random-access test on this number of randomly selected nodes.
29    #[arg(short, long)]
30    pub random: Option<usize>,
31
32    /// The number of repeats.
33    #[arg(short = 'R', long, default_value = "10")]
34    pub repeats: usize,
35
36    /// In random-access tests, test just access to the first successor.
37    #[arg(short = 'f', long)]
38    pub first: bool,
39
40    /// Static dispatch for speed tests (default BvGraph parameters).
41    #[arg(short = 'S', long = "static")]
42    pub _static: bool,
43
44    /// Test sequential high-speed offset/degree scanning.
45    #[arg(short = 'd', long)]
46    pub degrees: bool,
47
48    /// Do not test speed, but check that the sequential and random-access successor lists are the same.
49    #[arg(short = 'c', long)]
50    pub check: bool,
51
52    /// Expand offsets into a slice of usize before testing random-access
53    /// successor lists.
54    #[arg(long)]
55    pub slice: bool,
56}
57
58pub fn main(_global_args: GlobalArgs, args: CliArgs) -> Result<()> {
59    match get_endianness(&args.basename)?.as_str() {
60        #[cfg(feature = "be_bins")]
61        BE::NAME => match args._static {
62            true => bench_webgraph::<BE, Static>(args),
63            false => bench_webgraph::<BE, Dynamic>(args),
64        },
65        #[cfg(feature = "le_bins")]
66        LE::NAME => match args._static {
67            true => bench_webgraph::<LE, Static>(args),
68            false => bench_webgraph::<LE, Dynamic>(args),
69        },
70        e => panic!("Unknown endianness: {}", e),
71    }
72}
73
74fn bench_random(graph: impl RandomAccessGraph, samples: usize, repeats: usize, first: bool) {
75    // Random-access speed test
76    for _ in 0..repeats {
77        let mut rng = SmallRng::seed_from_u64(0);
78        let mut c: u64 = 0;
79        let num_nodes = graph.num_nodes();
80        let start = std::time::Instant::now();
81        if first {
82            for _ in 0..samples {
83                black_box(
84                    graph
85                        .successors(rng.random_range(0..num_nodes))
86                        .into_iter()
87                        .next()
88                        .unwrap_or(0),
89                );
90                c += 1;
91            }
92        } else {
93            for _ in 0..samples {
94                c += black_box(
95                    graph
96                        .successors(rng.random_range(0..num_nodes))
97                        .into_iter()
98                        .count() as u64,
99                );
100            }
101        }
102
103        println!(
104            "{}:    {:>20} ns/arc",
105            if first { "First" } else { "Random" },
106            (start.elapsed().as_secs_f64() / c as f64) * 1e9
107        );
108    }
109}
110
111fn bench_seq(graph: impl SequentialGraph, repeats: usize) {
112    for _ in 0..repeats {
113        let mut c: u64 = 0;
114
115        let start = std::time::Instant::now();
116        let mut iter = graph.iter();
117        while let Some((_, succ)) = iter.next() {
118            c += succ.into_iter().count() as u64;
119        }
120        println!(
121            "Sequential:{:>20} ns/arc",
122            (start.elapsed().as_secs_f64() / c as f64) * 1e9
123        );
124
125        assert_eq!(c, graph.num_arcs_hint().unwrap());
126    }
127}
128
129fn bench_webgraph<E: Endianness, D: Dispatch>(args: CliArgs) -> Result<()>
130where
131    MmapHelper<u32>: CodesReaderFactoryHelper<E>,
132    for<'a> LoadModeCodesReader<'a, E, Mmap>: BitSeek,
133{
134    if args.check {
135        let graph = BvGraph::with_basename(&args.basename)
136            .endianness::<E>()
137            .load()?;
138
139        let seq_graph = BvGraphSeq::with_basename(&args.basename)
140            .endianness::<E>()
141            .load()?;
142
143        let mut deg_reader = seq_graph.offset_deg_iter();
144
145        // Check that sequential and random-access interfaces return the same result
146        for_![ (node, seq_succ) in seq_graph {
147            let succ = graph.successors(node);
148
149            assert_eq!(deg_reader.next_degree()?, seq_succ.len());
150            assert_eq!(succ.collect_vec(), seq_succ.collect_vec());
151        }];
152    } else if args.degrees {
153        let seq_graph = BvGraphSeq::with_basename(&args.basename)
154            .endianness::<E>()
155            .load()?;
156
157        for _ in 0..args.repeats {
158            let mut deg_reader = seq_graph.offset_deg_iter();
159
160            let mut c: u64 = 0;
161            let start = std::time::Instant::now();
162            for _ in 0..seq_graph.num_nodes() {
163                c += black_box(deg_reader.next_degree()? as u64);
164            }
165            println!(
166                "Degrees Only:{:>20} ns/arc",
167                (start.elapsed().as_secs_f64() / c as f64) * 1e9
168            );
169
170            assert_eq!(c, seq_graph.num_arcs_hint().unwrap());
171        }
172    } else {
173        match (
174            args.random,
175            std::any::TypeId::of::<D>() == std::any::TypeId::of::<Dynamic>(),
176        ) {
177            (Some(samples), true) => {
178                if args.slice {
179                    bench_random(
180                        BvGraph::with_basename(&args.basename)
181                            .endianness::<E>()
182                            .dispatch::<Dynamic>()
183                            .mode::<Mmap>()
184                            .flags(MemoryFlags::TRANSPARENT_HUGE_PAGES | MemoryFlags::RANDOM_ACCESS)
185                            .load()?
186                            .offsets_to_slice(),
187                        samples,
188                        args.repeats,
189                        args.first,
190                    );
191                } else {
192                    bench_random(
193                        BvGraph::with_basename(&args.basename)
194                            .endianness::<E>()
195                            .dispatch::<Dynamic>()
196                            .mode::<Mmap>()
197                            .flags(MemoryFlags::TRANSPARENT_HUGE_PAGES | MemoryFlags::RANDOM_ACCESS)
198                            .load()?,
199                        samples,
200                        args.repeats,
201                        args.first,
202                    );
203                }
204            }
205            (Some(samples), false) => {
206                if args.slice {
207                    bench_random(
208                        BvGraph::with_basename(&args.basename)
209                            .endianness::<E>()
210                            .dispatch::<Static>()
211                            .mode::<Mmap>()
212                            .flags(MemoryFlags::TRANSPARENT_HUGE_PAGES | MemoryFlags::RANDOM_ACCESS)
213                            .load()?
214                            .offsets_to_slice(),
215                        samples,
216                        args.repeats,
217                        args.first,
218                    );
219                } else {
220                    bench_random(
221                        BvGraph::with_basename(&args.basename)
222                            .endianness::<E>()
223                            .dispatch::<Static>()
224                            .mode::<Mmap>()
225                            .flags(MemoryFlags::TRANSPARENT_HUGE_PAGES | MemoryFlags::RANDOM_ACCESS)
226                            .load()?,
227                        samples,
228                        args.repeats,
229                        args.first,
230                    );
231                }
232            }
233            (None, true) => {
234                bench_seq(
235                    BvGraphSeq::with_basename(&args.basename)
236                        .endianness::<E>()
237                        .dispatch::<Dynamic>()
238                        .mode::<Mmap>()
239                        .flags(MemoryFlags::TRANSPARENT_HUGE_PAGES | MemoryFlags::SEQUENTIAL)
240                        .load()?,
241                    args.repeats,
242                );
243            }
244            (None, false) => {
245                bench_seq(
246                    BvGraphSeq::with_basename(&args.basename)
247                        .endianness::<E>()
248                        .dispatch::<Static>()
249                        .mode::<Mmap>()
250                        .flags(MemoryFlags::TRANSPARENT_HUGE_PAGES | MemoryFlags::SEQUENTIAL)
251                        .load()?,
252                    args.repeats,
253                );
254            }
255        }
256    }
257    Ok(())
258}