georust / georust/proj

Performance of recreating the proj context

Open
#256 7 comments 0 reactions 0 assignees View on GitHub
Dominant language
Rust
Stars
186
Forks
63
PR merge metrics
No merged PRs in 30d

Description

Hey all,

I am using this crate for an app that renders in real time. Big transformations are of course done asynchronously, but to my mild surprise I also had to be careful when creating a new Proj instance. I was creating several new Proj instances every frame, but that took between 8-9ms per instance. Since I only have 16ms in a frame if I aim at 60fps, creating new Proj instances every frame is a no-go. I now work around it by implementing my own crs cache where I wrap every Proj instance in an Arc. This does the job just fine, but this surely is not the most ergonomic way to use the crate. If you want to verify the timing, you can add the following test in proj.rs:

```
#[test]
fn bench_proj_creation_time() {
let n = 1000;

// Warmup (would avoid first-run initialization)
for _ in 0..100 {
let _ = Proj::new("EPSG:4326").unwrap();
}

let start = Instant::now();

for _ in 0..n {
let _ = Proj::new("EPSG:4326").unwrap();
}

let elapsed = start.elapsed();

let avg = elapsed / n;

println!("Created {} Proj instances", n);
println!("Total time: {:?}", elapsed);
println!("Mean per Proj::new: {:?}", avg);
}
```

Then run it like so:
cargo test proj::test::bench_proj_creation_time -- --nocapture

I then get:
```
Created 1000 Proj instances
Total time: 8.821968393s
Mean per Proj::new: 8.821968ms
```

In contrast, in C I get a median time of 1.4 us and in python (!) I even get a mere ~7us. I can provide those scripts as well if you so desire.

I think the big difference is that in this crate we re-create the context every time we instantiate a new Proj object:
https://github.com/georust/proj/blob/f0ab553f59ef83862ceaefcdfcdd0add7446f219/src/proj.rs#L704

I imagine that every Proj instance owning it's own context is quite convenient and secure, but seeing both the Python and C implementations avoid this makes me wonder if we cannot do the same. Is there a particular reason we do it this way? Or is it mostly a convenience thing? I imagine we could maybe utilize a OnceCell or something like it, but before I dive too deep into the rabbit hole, maybe you guys can cut my investigation short if there is a known dealbreaker or deliberate design decision that would prevent some kind of shared context.

I would love to hear your thougts.

Kind regards,
Timo Millenaar

Contributor guide

No contributing guide indexed for this repository

Research direction

Start with src/proj.rs around line 704 to inspect how each Proj instance creates its context. Run the proposed cargo test benchmark and compare the reported creation cost; done means establishing whether context sharing is compatible with the crate's design and documenting or pursuing the resulting direction.

Written by the indexing model from the issue text.

Assessment

Tech stack
rust
Domain
performance
Issue type
Refactor
Difficulty
5/5
Estimated time
Over a week
Activity status
Quiet
Clarity
Needs clarification
Newbie friendliness
35/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.