-
Notifications
You must be signed in to change notification settings - Fork 48
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Memory usage in Flurry Hashmap #115
Comments
Huh, that's really interesting indeed, and suggests that the memory reclamation isn't kicking in at some point here. @JackThomson2 if you're looking for a project to get you back into more depths of flurry, this may be a good candidate! |
Hi Jon, sorry been away for a while. This looks an interesting problem, and does seem something to do with the reclamation not running for some reason. I'll definitely take a look at this, I'll update in this issue here what I find. So I had a little bit of time yesterday, here's what I've found so far:
Been doing some more investigating, this doesn't appear to happen in a simple console app so I believe it's something to do with the way actix is using the map. I'll keep digging I'll keep you posted on what I find next Update: Sorry for the lack of progress here, struggling to get much time. Been investigating the possibility of heap fragmentation. Using Jemallocator does seem to resolve the issue of calling One thing I'm investigating at the moment is how it performs in a serial console app, rather than in Actix, in this console app it appears to be behaving properly without the memory increases. So I'm looking into if it's how actix interacts with Flurry. More notes from my investigation
I'm going to try another async webserver and see if it's async related |
@JackThomson2 would you be able to find anything on above issue ? |
Hey sorry I didn't update here, been pretty busy at work. So a couple of new findings:
My suspicion is that its something to do with how actix uses multiple single threaded runtimes, but I haven't been able to investigate further yet. I'll keep you posted as I find more |
Thanks, @JackThomson2 for finding. My original problem started with hyper. I am not sure if there is an issue with Hyper or not. Let me try that |
Thanks for taking a look, when you test with hyper can you use a custom allocator such as jemalloc so we can try eliminate heap fragmentation being a potential cause |
Sure @JackThomson2 . WIll you be able to share the Axum example code you tried so I can compare it with hyper on the same machine and settings? |
Sure this is the code I was using use lazy_static::lazy_static;
use axum::{response::Html, routing::get, Router};
use std::net::SocketAddr;
use std::sync::Mutex;
use bytesize::ByteSize;
use sysinfo::{get_current_pid, ProcessExt, ProcessRefreshKind, RefreshKind, System, SystemExt};
use tikv_jemallocator::Jemalloc;
#[global_allocator]
static GLOBAL: Jemalloc = Jemalloc;
lazy_static! {
static ref APP_DATA: AppState = AppState::new();
}
struct AppState {
data: flurry::HashMap<String, String>,
sys: Mutex<System>,
}
impl AppState {
pub fn new() -> Self {
AppState {
data: flurry::HashMap::<String, String>::with_capacity(100663296),
sys: Mutex::new(System::new_with_specifics(
RefreshKind::new().with_processes(ProcessRefreshKind::new()),
)),
}
}
}
fn stats_2(data: &AppState) -> String {
let pid = get_current_pid().unwrap();
let mut sys = data.sys.lock().unwrap();
sys.refresh_process(pid);
let proc = sys.process(pid).unwrap();
let string = {
let map = &data.data.pin();
format!(
"Length: {}, Capacity: {}, Memory: {}, Virtual: {}\n",
map.len(),
map.len(),
ByteSize::b(proc.memory()).to_string_as(true),
ByteSize::b(proc.virtual_memory()).to_string_as(true)
)
};
string
}
#[tokio::main]
async fn main() {
// build our application with a route
let app = Router::new()
.route("/add", get(add_handler))
.route("/clear", get(clear_handler))
.route("/", get(handler));
// run it
let addr = SocketAddr::from(([127, 0, 0, 1], 3000));
println!("listening on {}", addr);
axum::Server::bind(&addr)
.serve(app.into_make_service())
.await
.unwrap();
}
async fn handler() -> Html<&'static str> {
Html("<h1>Hello, World!</h1>")
}
async fn add_handler() -> String {
let size;
let init_size;
{
let max_entries = 100663296 as u64;
let m = &APP_DATA.data;
init_size = m.len();
let adding = m.pin();
for i in 0..max_entries / 2 {
adding.insert(format!("str-{i}"), format!("str-{i}-{i}"));
}
size = m.len();
}
let stats1 = stats_2(&APP_DATA);
format!(
"initial count {init_size}, count {size}! from thread {:?} - stats {stats1}\n",
std::thread::current().id()
)
}
async fn clear_handler() -> String {
let size;
let init_size;
{
let m = &APP_DATA.data;
init_size = m.len();
m.pin().clear();
// unsafe { malloc_trim(0) };
size = m.len();
}
let stats1 = stats_2(&APP_DATA);
format!(
"initial count {init_size}, count {size}! from thread {:?} - stats {stats1}\n",
std::thread::current().id()
)
} |
@JackThomson2 what output is coming for add -> clear -> add -> clear on the above snippet for you ? |
I don't exact numbers, but what I found was memory increased the first 2 cycles then eventually dropped and stayed consistent |
So some exciting news. I've made a little breakthrough and figured out the exact location of our issue. It appears to be with the delayed allocations in the shield / waiting for it to drop. I will now dig further into the implementation of Seize to figure out how we're seeing this issue. But I feel we're close to a fix for this! Thanks again @ss025 for flagging this up. |
Just saw this issue. If you need any help or guidance regarding seize please let me know! |
Hey @ibraheemdev thank you I may send you and email shortly feel like I'm going in circles here. I think the |
So I've come full circle here. I think the issue is actually just memory fragmentation. I switched to Jemalloc and hooked into their API to retrieve actual bytes allocated and it's consistent between The question is why the I think the real solution may be too implement the preloading of nodes and reusing them as suggested by @ibraheemdev here: #80 (comment) I've been having a play with implementing this to see how it affects the memory profile (at the moment it's very hacky and a lot slower but looking at memory usage) |
So I had some time to work on this, and I implemented some memory optimizations on an experimental branch. It cuts the node memory overhead in half by moving batches to a separate allocation instead of the linked list. crossbeam-epoch implements this batches this way as well, and I think it's a good tradeoff in general because the allocation is still amortized to once per-batch. If either of you want to run your tests against the branch and report any findings here that would be helpful. |
The seize improvements are released in |
Hi I am trying to use flurry hashmap in one of my projects and want to understand memory usage and garbage collection.
I am trying a simple actix web appplication with flurry hashmap by inserting same key/value again and again. Few observation are
add -> clear -> add
repeatedly on same key/values eventually leads to OOM error and application is killed.Output of program
main.rs
Cargo.toml
The text was updated successfully, but these errors were encountered: