-
Notifications
You must be signed in to change notification settings - Fork 2.7k
Decommit instance memory after a runtime call on Linux #8998
Changes from all commits
c97a77e
74cf151
53e29c9
ec3cf69
5d07a20
2e07a0e
b4ee835
3943a69
dfe1170
75f04d5
56e165d
caaa7b5
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,73 @@ | ||
| // This file is part of Substrate. | ||
|
|
||
| // Copyright (C) 2021 Parity Technologies (UK) Ltd. | ||
| // SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 | ||
|
|
||
| // This program is free software: you can redistribute it and/or modify | ||
| // it under the terms of the GNU General Public License as published by | ||
| // the Free Software Foundation, either version 3 of the License, or | ||
| // (at your option) any later version. | ||
|
|
||
| // This program is distributed in the hope that it will be useful, | ||
| // but WITHOUT ANY WARRANTY; without even the implied warranty of | ||
| // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||
| // GNU General Public License for more details. | ||
|
|
||
| // You should have received a copy of the GNU General Public License | ||
| // along with this program. If not, see <https://www.gnu.org/licenses/>. | ||
|
|
||
| //! Tests that are only relevant for Linux. | ||
|
|
||
| // Constrain this only to wasmtime for the time being. Without this rustc will complain on unused | ||
| // imports and items. The alternative is to plop `cfg(feature = wasmtime)` everywhere which seems | ||
| // borthersome. | ||
| #![cfg(feature = "wasmtime")] | ||
|
|
||
| use crate::WasmExecutionMethod; | ||
| use super::mk_test_runtime; | ||
| use codec::Encode as _; | ||
|
|
||
| mod smaps; | ||
|
|
||
| use self::smaps::Smaps; | ||
|
|
||
| #[test] | ||
| fn memory_consumption_compiled() { | ||
pepyakin marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| // This aims to see if linear memory stays backed by the physical memory after a runtime call. | ||
| // | ||
| // For that we make a series of runtime calls, probing the RSS for the VMA matching the linear | ||
| // memory. After the call we expect RSS to be equal to 0. | ||
|
|
||
| let runtime = mk_test_runtime(WasmExecutionMethod::Compiled, 1024); | ||
|
|
||
| let instance = runtime.new_instance().unwrap(); | ||
| let heap_base = instance | ||
| .get_global_const("__heap_base") | ||
| .expect("`__heap_base` is valid") | ||
| .expect("`__heap_base` exists") | ||
| .as_i32() | ||
| .expect("`__heap_base` is an `i32`"); | ||
|
|
||
| fn probe_rss(instance: &dyn sc_executor_common::wasm_runtime::WasmInstance) -> usize { | ||
| let base_addr = instance.linear_memory_base_ptr().unwrap() as usize; | ||
| Smaps::new().get_rss(base_addr).expect("failed to get rss") | ||
| } | ||
|
|
||
| instance | ||
| .call_export( | ||
| "test_dirty_plenty_memory", | ||
| &(heap_base as u32, 1u32).encode(), | ||
| ) | ||
| .unwrap(); | ||
| let probe_1 = probe_rss(&*instance); | ||
| instance | ||
| .call_export( | ||
| "test_dirty_plenty_memory", | ||
| &(heap_base as u32, 1024u32).encode(), | ||
| ) | ||
| .unwrap(); | ||
| let probe_2 = probe_rss(&*instance); | ||
|
|
||
| assert_eq!(probe_1, 0); | ||
| assert_eq!(probe_2, 0); | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,82 @@ | ||
| // This file is part of Substrate. | ||
|
|
||
| // Copyright (C) 2017-2021 Parity Technologies (UK) Ltd. | ||
| // SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 | ||
|
|
||
| // This program is free software: you can redistribute it and/or modify | ||
| // it under the terms of the GNU General Public License as published by | ||
| // the Free Software Foundation, either version 3 of the License, or | ||
| // (at your option) any later version. | ||
|
|
||
| // This program is distributed in the hope that it will be useful, | ||
| // but WITHOUT ANY WARRANTY; without even the implied warranty of | ||
| // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||
| // GNU General Public License for more details. | ||
|
|
||
| // You should have received a copy of the GNU General Public License | ||
| // along with this program. If not, see <https://www.gnu.org/licenses/>. | ||
|
|
||
| //! A tool for extracting information about the memory consumption of the current process from | ||
| //! the procfs. | ||
| use std::ops::Range; | ||
| use std::collections::BTreeMap; | ||
|
|
||
| /// An interface to the /proc/self/smaps | ||
| /// | ||
| /// See docs about [procfs on kernel.org][procfs] | ||
| /// | ||
| /// [procfs]: https://www.kernel.org/doc/html/latest/filesystems/proc.html | ||
| pub struct Smaps(Vec<(Range<usize>, BTreeMap<String, usize>)>); | ||
|
|
||
| impl Smaps { | ||
| pub fn new() -> Self { | ||
| let regex_start = regex::RegexBuilder::new("^([0-9a-f]+)-([0-9a-f]+)") | ||
| .multi_line(true) | ||
| .build() | ||
| .unwrap(); | ||
| let regex_kv = regex::RegexBuilder::new(r#"^([^:]+):\s*(\d+) kB"#) | ||
| .multi_line(true) | ||
| .build() | ||
| .unwrap(); | ||
| let smaps = std::fs::read_to_string("/proc/self/smaps").unwrap(); | ||
| let boundaries: Vec<_> = regex_start | ||
| .find_iter(&smaps) | ||
| .map(|matched| matched.start()) | ||
| .chain(std::iter::once(smaps.len())) | ||
| .collect(); | ||
|
|
||
| let mut output = Vec::new(); | ||
| for window in boundaries.windows(2) { | ||
| let chunk = &smaps[window[0]..window[1]]; | ||
| let caps = regex_start.captures(chunk).unwrap(); | ||
| let start = usize::from_str_radix(caps.get(1).unwrap().as_str(), 16).unwrap(); | ||
| let end = usize::from_str_radix(caps.get(2).unwrap().as_str(), 16).unwrap(); | ||
|
|
||
| let values = regex_kv | ||
| .captures_iter(chunk) | ||
| .map(|cap| { | ||
| let key = cap.get(1).unwrap().as_str().to_owned(); | ||
| let value = cap.get(2).unwrap().as_str().parse().unwrap(); | ||
| (key, value) | ||
| }) | ||
| .collect(); | ||
|
|
||
| output.push((start..end, values)); | ||
| } | ||
|
|
||
| Self(output) | ||
| } | ||
|
|
||
| fn get_map(&self, addr: usize) -> &BTreeMap<String, usize> { | ||
| &self.0 | ||
| .iter() | ||
| .find(|(range, _)| addr >= range.start && addr < range.end) | ||
| .unwrap() | ||
| .1 | ||
| } | ||
|
|
||
| pub fn get_rss(&self, addr: usize) -> Option<usize> { | ||
| self.get_map(addr).get("Rss").cloned() | ||
| } | ||
|
Comment on lines
+71
to
+81
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. How sure are you that the whole instance memory is kept in one mapping? I think they can also be split up in multiple consecutive mappings (at least on macOS that is the case).
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This is Linux only code so we don't care about macOS here. Yes, potentially they can be split up on several mappings. The opposite is also possible AFAIU, when several instances are packed into a single VMA. Note though that the solution with We generally cannot be 100% sure what wasmtime does under the hood, so this is the best effort. But hey it's not the end of the world if this test fails. I see no reason it to be not a single mapping though, at least on Linux. It is simpler to implement, most efficient, etc |
||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -415,6 +415,43 @@ impl InstanceWrapper { | |
| slice::from_raw_parts_mut(ptr, len) | ||
| } | ||
| } | ||
|
|
||
| /// Returns the pointer to the first byte of the linear memory for this instance. | ||
| pub fn base_ptr(&self) -> *const u8 { | ||
| self.memory.data_ptr() | ||
| } | ||
|
|
||
| /// Removes physical backing from the allocated linear memory. This leads to returning the memory | ||
| /// back to the system. While the memory is zeroed this is considered as a side-effect and is not | ||
| /// relied upon. Thus this function acts as a hint. | ||
| pub fn decommit(&self) { | ||
| if self.memory.data_size() == 0 { | ||
| return; | ||
| } | ||
|
|
||
| cfg_if::cfg_if! { | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We don't need
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. You mean that There are different options there to make it work on macOS, but those are out of scope for this PR. When the time comes though it will be clear where to integrate them : )
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I mean we don't need the Could be just
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. (Assuming you meant Yeah, true, not necessary. I figured I use it because:
It's not something I am married to, so can change to a plain
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yeah fine |
||
| if #[cfg(target_os = "linux")] { | ||
| use std::sync::Once; | ||
|
|
||
| unsafe { | ||
| let ptr = self.memory.data_ptr(); | ||
| let len = self.memory.data_size(); | ||
|
|
||
| // Linux handles MADV_DONTNEED reliably. The result is that the given area | ||
| // is unmapped and will be zeroed on the next pagefault. | ||
| if libc::madvise(ptr as _, len, libc::MADV_DONTNEED) != 0 { | ||
| static LOGGED: Once = Once::new(); | ||
| LOGGED.call_once(|| { | ||
| log::warn!( | ||
| "madvise(MADV_DONTNEED) failed: {}", | ||
| std::io::Error::last_os_error(), | ||
| ); | ||
| }); | ||
| } | ||
| } | ||
| } | ||
| } | ||
| } | ||
| } | ||
|
|
||
| impl runtime_blob::InstanceGlobals for InstanceWrapper { | ||
|
|
||
Uh oh!
There was an error while loading. Please reload this page.