Hello everyone! I have a project that I'm using the egui
crate for and its running slow. I'm not sure why its running slow and I'm not sure how to post it on here. The program is going to be an image viewer using an API, the image is loaded then a description of the image. Everything loads fine, but the program runs slow, the menus open slow, the button hovering is active highlighting is slow. I don't know where to start as I'm very new to Rust, I'm actually making this program to help me learn Rust. Should I use some kind of paste bin or should i give the link to the GutHub project?
Are you compiling the project in release mode i.e. with cargo build --release
?
Rust code is known to be slow in debug mode (i.e. no --release
flag) because no optimizations are performed whatsoever.
I tried compiling with the --release
flag, but I don't notice any improvement. Its like the program is taking too much resources. I'm using reqwest::blocking
to get images for now. It's laggy.
Yes. This is a great way to get feedback and help.
If you run it with cargo run
you must pass --release
there also.
Yes, I have tried this cargo run --release
and it doesn't help. I feel like its something I'm doing vs Rust just being slow. There are more advanced egui examples and applications seem to run just fine.
It looks like you are using blocking I/O to download a JSON file on every frame. Yes, that will certainly feel slow. Caching the parsed JSON makes it feel a lot more responsive:
pub fn get_pic_data_blocking(&mut self) -> Result<(String, String), reqwest::Error> {
match &self.image_cache {
Some(cache) => Ok(cache.clone()),
None => {
let data = reqwest::blocking::get(URL)?
.text()
.expect("Failed to retrieve image from API...");
let json_object = json::parse(&data).expect("Failed to parse image data...");
let image_data: (String, String) = (
json_object["hdurl"].to_string(),
json_object["explanation"].to_string(),
);
self.image_cache = Some(image_data.clone());
Ok(image_data)
}
}
}
Full diff...
diff --git a/src/app.rs b/src/app.rs
index b82ff00..5e86aec 100644
--- a/src/app.rs
+++ b/src/app.rs
@@ -18,6 +17,7 @@ enum Views {
pub struct SpacePixUi {
current_view: Views,
image_url: String,
+ image_cache: Option<(String, String)>,
image_desc: String,
}
@@ -26,6 +26,7 @@ impl Default for SpacePixUi {
Self {
current_view: Views::APOD,
image_url: String::from("Beans"), // This should point to some default logo file for now
+ image_cache: None,
image_desc: String::from("Beans"), // This can say something like: "Welcome to spacepix!"
}
}
@@ -75,24 +74,33 @@ impl SpacePixUi {
Ok(image_data)
}
- pub fn get_pic_data_blocking() -> Result<(String, String), reqwest::Error> {
- let data = reqwest::blocking::get(URL)?
- .text()
- .expect("Failed to retrieve image from API...");
+ pub fn get_pic_data_blocking(&mut self) -> Result<(String, String), reqwest::Error> {
+ match &self.image_cache {
+ Some(cache) => Ok(cache.clone()),
+ None => {
+ let data = reqwest::blocking::get(URL)?
+ .text()
+ .expect("Failed to retrieve image from API...");
+
+ let json_object = json::parse(&data).expect("Failed to parse image data...");
+ let image_data: (String, String) = (
+ json_object["hdurl"].to_string(),
+ json_object["explanation"].to_string(),
+ );
- let json_object = json::parse(&data).expect("Failed to parse image data...");
- let image_data: (String, String) = (
- json_object["hdurl"].to_string(),
- json_object["explanation"].to_string(),
- );
+ self.image_cache = Some(image_data.clone());
- Ok(image_data)
+ Ok(image_data)
+ }
+ }
}
}
impl eframe::App for SpacePixUi {
fn update(&mut self, ctx: &egui::Context, frame: &mut eframe::Frame) {
- let image_data = self::SpacePixUi::get_pic_data_blocking().expect("Failed to get image...");
+ let image_data = self
+ .get_pic_data_blocking()
+ .expect("Failed to get image...");
self.image_desc = image_data.1;
self.image_url = image_data.0;
let image = egui::Image::from_uri(self.image_url.clone()); // I had to clone here for some reason...
I'm not an egui expert, but it looks like you're fetching a remote resource many times a second. That's surely slowing you down (and may get your IP/API key blocked or throttled remotely).[1]
Try cacheing that data and only updating it every few minutes or on demand or such.
note the rate limits ↩︎
Dang, I thought the blocking was the wrong way to go, I wrote an async version of get_pic_data()
, but I don't know how to use it with egui.
hahaa they probably thought it was a DDos or something everytime I ran the program.
You can use blocking I/O. It's a much nicer user experience to do it in a thread because it can cause the UI to stall for an indeterminate amount of time.
Using the async fn will need a tokio
executor running. You will also need a thread to block on the future, or (better) poll a channel in your update
function. I put this example together long ago to demonstrate, but it is not the best way to launch the executor: egui-tokio-example/src/main.rs at main · parasyte/egui-tokio-example
A much better way is just using the normal tokio::main
macro like this: example-blog-client/src/main.rs at main · ChrisRega/example-blog-client (edit: I don't know if this actually works on macOS! That platform doesn't allow event loops on the main GUI thread, AFAIK. Which is why I did it the wonky way.)
(edit 2: It does mistakenly "work" on macOS, for really bad reasons! The language and libraries have no way to detect nested event loops and the non-cooperation it causes. See Tokio + Winit? · tokio-rs/tokio · Discussion #2953 · GitHub and Bridging with sync code | Tokio - An asynchronous Rust runtime -- What actually happens when you do this on macOS is that things appear to be ok until the application is closed, and it crashes with Trace/BPT trap: 5
, an uncaught exception! This is basically "here be dragons" where anything can happen and much of it will not be good.)
This worked beautifully! It's buttery smooth now, thank you!
Although the problem solved, sometimes you do not have so clear cases issuing slowness. You can use gprofng to find causes and fix them.