Skip to content

Commit 8def963

Browse files
committed
Add read::EntriesRaw::read_attribute_inline
Also change `read::EntriesRaw::read_attribute` to never inline. The goal is to let users decide if they want performance or code size, while at the same time making benchmarks more reliable, instead of depending on whether the compiler decided to inline or not. The change to always inline the internal `parse_attribute` means that the `EntriesCursor` and `EntriesTree` benchmarks are improved.
1 parent 0d070b3 commit 8def963

File tree

2 files changed

+74
-8
lines changed

2 files changed

+74
-8
lines changed

benches/bench.rs

Lines changed: 58 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,11 @@ use criterion::{Bencher, Criterion, criterion_main};
22
use std::hint::black_box;
33

44
use gimli::{
5-
AttributeValue, DebugAbbrev, DebugAddr, DebugAddrBase, DebugAranges, DebugInfo, DebugLine,
6-
DebugLineOffset, DebugLoc, DebugLocLists, DebugPubNames, DebugPubTypes, DebugRanges,
7-
DebugRngLists, Encoding, EndianSlice, EntriesTreeNode, Expression, LittleEndian, LocationLists,
8-
NativeEndian, Operation, RangeLists, RangeListsOffset, Reader, ReaderOffset, leb128,
5+
Attribute, AttributeSpecification, AttributeValue, DebugAbbrev, DebugAddr, DebugAddrBase,
6+
DebugAranges, DebugInfo, DebugLine, DebugLineOffset, DebugLoc, DebugLocLists, DebugPubNames,
7+
DebugPubTypes, DebugRanges, DebugRngLists, Encoding, EndianSlice, EntriesRaw, EntriesTreeNode,
8+
Expression, LittleEndian, LocationLists, NativeEndian, Operation, RangeLists, RangeListsOffset,
9+
Reader, ReaderOffset, leb128,
910
};
1011
use std::env;
1112
use std::fs::File;
@@ -129,7 +130,11 @@ fn bench_read() {
129130
);
130131
c.bench_function("read::EntriesTree", bench_entries_tree::<1>);
131132
c.bench_function("read::EntriesTree (attrs twice)", bench_entries_tree::<2>);
132-
c.bench_function("read::EntriesRaw", bench_entries_raw);
133+
c.bench_function("read::EntriesRaw::read_attribute", bench_entries_raw_call);
134+
c.bench_function(
135+
"read::EntriesRaw::read_attribute_inline",
136+
bench_entries_raw_inline,
137+
);
133138

134139
let mut c = Criterion::default().configure_from_args();
135140
c.bench_function("parse .debug_abbrev", bench_parsing_debug_abbrev);
@@ -291,7 +296,51 @@ fn parse_debug_info_tree<const COUNT: usize, R: Reader>(node: EntriesTreeNode<R>
291296
}
292297
}
293298

294-
fn bench_entries_raw(b: &mut Bencher) {
299+
fn bench_entries_raw_call(b: &mut Bencher) {
300+
let debug_abbrev = read_section("debug_abbrev");
301+
let debug_abbrev = DebugAbbrev::new(&debug_abbrev, LittleEndian);
302+
303+
let debug_info = read_section("debug_info");
304+
305+
b.iter(|| {
306+
let debug_info = DebugInfo::new(&debug_info, LittleEndian);
307+
308+
let mut iter = debug_info.units();
309+
while let Some(unit) = iter.next().expect("Should parse compilation unit") {
310+
let abbrevs = unit
311+
.abbreviations(&debug_abbrev)
312+
.expect("Should parse abbreviations");
313+
314+
let mut raw = unit
315+
.entries_raw(&abbrevs, None)
316+
.expect("Should have entries");
317+
while !raw.is_empty() {
318+
if let Some(abbrev) = raw
319+
.read_abbreviation()
320+
.expect("Should parse abbreviation code")
321+
{
322+
for spec in abbrev.attributes().iter().cloned() {
323+
let attr = read_attribute(&mut raw, spec).expect("Should parse attribute");
324+
let name = attr.name();
325+
black_box(name);
326+
let value = attr.raw_value();
327+
black_box(value);
328+
}
329+
}
330+
}
331+
}
332+
});
333+
}
334+
335+
#[inline(never)]
336+
fn read_attribute<R: Reader>(
337+
input: &mut EntriesRaw<R>,
338+
spec: AttributeSpecification,
339+
) -> gimli::Result<Attribute<R>> {
340+
input.read_attribute_inline(spec)
341+
}
342+
343+
fn bench_entries_raw_inline(b: &mut Bencher) {
295344
let debug_abbrev = read_section("debug_abbrev");
296345
let debug_abbrev = DebugAbbrev::new(&debug_abbrev, LittleEndian);
297346

@@ -315,7 +364,9 @@ fn bench_entries_raw(b: &mut Bencher) {
315364
.expect("Should parse abbreviation code")
316365
{
317366
for spec in abbrev.attributes().iter().cloned() {
318-
let attr = raw.read_attribute(spec).expect("Should parse attribute");
367+
let attr = raw
368+
.read_attribute_inline(spec)
369+
.expect("Should parse attribute");
319370
let name = attr.name();
320371
black_box(name);
321372
let value = attr.raw_value();

src/read/unit.rs

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2099,6 +2099,9 @@ fn allow_section_offset(name: constants::DwAt, version: u16) -> bool {
20992099
}
21002100
}
21012101

2102+
// This function is performance critical. It is called from a small number of
2103+
// wrapper functions. We always inline it into the wrapper functions.
2104+
#[inline(always)]
21022105
pub(crate) fn parse_attribute<R: Reader>(
21032106
input: &mut R,
21042107
encoding: Encoding,
@@ -2567,11 +2570,23 @@ impl<'abbrev, 'unit, R: Reader> EntriesRaw<'abbrev, 'unit, R> {
25672570
}
25682571

25692572
/// Read an attribute.
2570-
#[inline]
2573+
///
2574+
/// This function is never inlined. Consider using `read_attribute_inline` instead
2575+
/// if you only call this from a small number of places.
2576+
#[inline(never)]
25712577
pub fn read_attribute(&mut self, spec: AttributeSpecification) -> Result<Attribute<R>> {
25722578
parse_attribute(&mut self.input, self.unit.encoding(), spec)
25732579
}
25742580

2581+
/// Read an attribute.
2582+
///
2583+
/// Identical to `read_attribute`, but has the `#[inline(always)]` attribute.
2584+
/// This allows better optimisation at the cost of code size.
2585+
#[inline(always)]
2586+
pub fn read_attribute_inline(&mut self, spec: AttributeSpecification) -> Result<Attribute<R>> {
2587+
parse_attribute(&mut self.input, self.unit.encoding(), spec)
2588+
}
2589+
25752590
/// Skip all the attributes of an abbreviation.
25762591
#[inline]
25772592
pub fn skip_attributes(&mut self, specs: &[AttributeSpecification]) -> Result<()> {

0 commit comments

Comments
 (0)