-
Notifications
You must be signed in to change notification settings - Fork 282
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Bug 1903977 - Add a blob tile pool. r=gfx-reviewers,gw
The tile pool keeps a strong reference to all of its allocations and reuses them when their reference count gets down to one. Jemalloc only uses thread-local arenas for small allocations (<496 bytes) so it does not matter what thread the blob tiles which are typically much larger, are deallocated in. Differential Revision: https://phabricator.services.mozilla.com/D218547
- Loading branch information
Showing
7 changed files
with
172 additions
and
8 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,151 @@ | ||
// This Source Code Form is subject to the terms of the Mozilla Public | ||
// License, v. 2.0. If a copy of the MPL was not distributed with this | ||
// file, You can obtain one at http://mozilla.org/MPL/2.0/. | ||
|
||
use std::sync::Arc; | ||
|
||
const NUM_TILE_BUCKETS: usize = 6; | ||
|
||
/// A pool of blob tile buffers to mitigate the overhead of | ||
/// allocating and deallocating blob tiles. | ||
/// | ||
/// The pool keeps a strong reference to each allocated buffers and | ||
/// reuses the ones with a strong count of 1. | ||
pub struct BlobTilePool { | ||
largest_size_class: usize, | ||
buckets: [Vec<Arc<Vec<u8>>>; NUM_TILE_BUCKETS], | ||
} | ||
|
||
impl BlobTilePool { | ||
pub fn new() -> Self { | ||
// The default max tile size is actually 256, using 512 here | ||
// so that this still works when experimenting with larger | ||
// tile sizes. If we ever make larger adjustments, the buckets | ||
// should be changed accordingly. | ||
let max_tile_size = 512; | ||
BlobTilePool { | ||
largest_size_class: max_tile_size * max_tile_size * 4, | ||
buckets: [ | ||
Vec::with_capacity(32), | ||
Vec::with_capacity(32), | ||
Vec::with_capacity(32), | ||
Vec::with_capacity(32), | ||
Vec::with_capacity(32), | ||
Vec::with_capacity(32), | ||
], | ||
} | ||
} | ||
|
||
/// Get or allocate a tile buffer of the requested size. | ||
/// | ||
/// The returned buffer is zero-inizitalized. | ||
/// The length of the returned buffer is equal to the requested size, | ||
/// however the buffer may be allocated with a larger capacity to | ||
/// confirm to the pool's corresponding bucket tile size. | ||
pub fn get_buffer(&mut self, requested_size: usize) -> MutableTileBuffer { | ||
assert!(requested_size <= self.largest_size_class); | ||
|
||
let (bucket_idx, cap) = self.bucket_and_size(requested_size); | ||
let bucket = &mut self.buckets[bucket_idx]; | ||
let mut selected_idx = None; | ||
for (buf_idx, buffer) in bucket.iter().enumerate() { | ||
if Arc::strong_count(buffer) == 1 { | ||
selected_idx = Some(buf_idx); | ||
break; | ||
} | ||
} | ||
|
||
let ptr; | ||
let strong_ref; | ||
if let Some(idx) = selected_idx { | ||
{ | ||
// This works because we just ensured the pool has the only strong | ||
// ref to the buffer. | ||
let buffer = Arc::get_mut(&mut bucket[idx]).unwrap(); | ||
debug_assert!(buffer.capacity() >= requested_size); | ||
// Ensure the length is equal to the requested size. It's not | ||
// strictly necessay for the tile pool but the texture upload | ||
// code relies on it. | ||
unsafe { buffer.set_len(requested_size); } | ||
|
||
// zero-initialize | ||
buffer.fill(0); | ||
|
||
ptr = buffer.as_mut_ptr(); | ||
} | ||
strong_ref = Arc::clone(&bucket[idx]); | ||
} else { | ||
// Allocate a buffer with the adequate capacity for the requested | ||
// size's bucket. | ||
let mut buf = vec![0; cap]; | ||
// Force the length to be the requested size. | ||
unsafe { buf.set_len(requested_size) }; | ||
|
||
ptr = buf.as_mut_ptr(); | ||
strong_ref = Arc::new(buf); | ||
// Track the new buffer. | ||
bucket.push(Arc::clone(&strong_ref)); | ||
}; | ||
|
||
MutableTileBuffer { | ||
ptr, | ||
strong_ref, | ||
} | ||
} | ||
|
||
fn bucket_and_size(&self, size: usize) -> (usize, usize) { | ||
let mut next_size_class = self.largest_size_class / 4; | ||
let mut idx = 0; | ||
while size < next_size_class && idx < NUM_TILE_BUCKETS - 1 { | ||
next_size_class /= 4; | ||
idx += 1; | ||
} | ||
|
||
(idx, next_size_class * 4) | ||
} | ||
|
||
/// Go over all allocated tile buffers. For each bucket, deallocate some buffers | ||
/// until the number of unused buffer is more than half of the buffers for that | ||
/// bucket. | ||
/// | ||
/// In practice, if called regularly, this gradually lets go of blob tiles when | ||
/// they are not used. | ||
pub fn cleanup(&mut self) { | ||
for bucket in &mut self.buckets { | ||
let threshold = bucket.len() / 2; | ||
let mut num_available = 0; | ||
bucket.retain(&mut |buffer: &Arc<Vec<u8>>| { | ||
if Arc::strong_count(buffer) > 1 { | ||
return true; | ||
} | ||
|
||
num_available += 1; | ||
num_available < threshold | ||
}); | ||
} | ||
} | ||
} | ||
|
||
|
||
// The role of tile buffer is to encapsulate an Arc to the underlying buffer | ||
// with a reference count of at most 2 and a way to view the buffer's content | ||
// as a mutable slice, even though the reference count may be more than 1. | ||
// The safety of this relies on the other strong reference being held by the | ||
// tile pool which never accesses the buffer's content, so the only reference | ||
// that can access it is the `TileBuffer` itself. | ||
pub struct MutableTileBuffer { | ||
strong_ref: Arc<Vec<u8>>, | ||
ptr: *mut u8, | ||
} | ||
|
||
impl MutableTileBuffer { | ||
pub fn as_mut_slice(&mut self) -> &mut[u8] { | ||
unsafe { std::slice::from_raw_parts_mut(self.ptr, self.strong_ref.len()) } | ||
} | ||
|
||
pub fn into_arc(self) -> Arc<Vec<u8>> { | ||
self.strong_ref | ||
} | ||
} | ||
|
||
unsafe impl Send for MutableTileBuffer {} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters