rework accounts hash calc dedup to avoid kernel file error (#33195)

* in hash calc, calculate max_inclusive_num_pubkeys

* in hash calc, dedup uses mmap files to avoid os panic

* as_mut_ptr

* remove unsafe code

* refactor count in hash files

---------

Co-authored-by: HaoranYi <haoran.yi@solana.com>
This commit is contained in:
Jeff Washington (jwash) 2023-09-11 10:57:21 -07:00 committed by GitHub
parent 7fc6fea8d8
commit 4dfe62a2f0
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
1 changed files with 84 additions and 52 deletions

View File

@ -19,8 +19,7 @@ use {
std::{
borrow::Borrow,
convert::TryInto,
fs::File,
io::{BufWriter, Write},
io::{Seek, SeekFrom, Write},
path::PathBuf,
sync::{
atomic::{AtomicU64, AtomicUsize, Ordering},
@ -33,81 +32,96 @@ pub const MERKLE_FANOUT: usize = 16;
/// 1 file containing account hashes sorted by pubkey, mapped into memory
struct MmapAccountHashesFile {
/// raw slice of `Hash` values. Can be a larger slice than `count`
mmap: MmapMut,
/// # of valid Hash entries in `mmap`
count: usize,
}
impl MmapAccountHashesFile {
/// return a slice of account hashes starting at 'index'
fn read(&self, index: usize) -> &[Hash] {
let start = std::mem::size_of::<Hash>() * index;
let item_slice: &[u8] = &self.mmap[start..];
let item_slice: &[u8] = &self.mmap[start..self.count * std::mem::size_of::<Hash>()];
let remaining_elements = item_slice.len() / std::mem::size_of::<Hash>();
unsafe {
let item = item_slice.as_ptr() as *const Hash;
std::slice::from_raw_parts(item, remaining_elements)
}
}
/// write a hash to the end of mmap file.
fn write(&mut self, hash: &Hash) {
let start = self.count * std::mem::size_of::<Hash>();
let end = start + std::mem::size_of::<Hash>();
self.mmap[start..end].copy_from_slice(hash.as_ref());
self.count += 1;
}
}
/// 1 file containing account hashes sorted by pubkey
pub struct AccountHashesFile {
/// # hashes and an open file that will be deleted on drop. None if there are zero hashes to represent, and thus, no file.
count_and_writer: Option<(usize, BufWriter<File>)>,
writer: Option<MmapAccountHashesFile>,
/// The directory where temporary cache files are put
dir_for_temp_cache_files: PathBuf,
/// # bytes allocated
capacity: usize,
}
impl AccountHashesFile {
/// map the file into memory and return a reader that can access it by slice
fn get_reader(&mut self) -> Option<(usize, MmapAccountHashesFile)> {
std::mem::take(&mut self.count_and_writer).map(|(count, writer)| {
let file = Some(writer.into_inner().unwrap());
(
count,
MmapAccountHashesFile {
mmap: unsafe { MmapMut::map_mut(file.as_ref().unwrap()).unwrap() },
},
)
})
/// return a mmap reader that can be accessed by slice
fn get_reader(&mut self) -> Option<MmapAccountHashesFile> {
std::mem::take(&mut self.writer)
}
/// # hashes stored in this file
pub fn count(&self) -> usize {
self.count_and_writer
self.writer
.as_ref()
.map(|(count, _)| *count)
.map(|writer| writer.count)
.unwrap_or_default()
}
/// write 'hash' to the file
/// If the file isn't open, create it first.
pub fn write(&mut self, hash: &Hash) {
if self.count_and_writer.is_none() {
if self.writer.is_none() {
// we have hashes to write but no file yet, so create a file that will auto-delete on drop
self.count_and_writer = Some((
0,
BufWriter::new(
tempfile_in(&self.dir_for_temp_cache_files).unwrap_or_else(|err| {
panic!(
"Unable to create file within {}: {err}",
self.dir_for_temp_cache_files.display()
)
}),
),
));
}
let count_and_writer = self.count_and_writer.as_mut().unwrap();
count_and_writer
.1
.write_all(hash.as_ref())
.unwrap_or_else(|err| {
let mut data = tempfile_in(&self.dir_for_temp_cache_files).unwrap_or_else(|err| {
panic!(
"Unable to write file within {}: {err}",
"Unable to create file within {}: {err}",
self.dir_for_temp_cache_files.display()
)
});
count_and_writer.0 += 1;
// Theoretical performance optimization: write a zero to the end of
// the file so that we won't have to resize it later, which may be
// expensive.
data.seek(SeekFrom::Start((self.capacity - 1) as u64))
.unwrap();
data.write_all(&[0]).unwrap();
data.rewind().unwrap();
data.flush().unwrap();
//UNSAFE: Required to create a Mmap
let map = unsafe { MmapMut::map_mut(&data) };
let map = map.unwrap_or_else(|e| {
error!(
"Failed to map the data file (size: {}): {}.\n
Please increase sysctl vm.max_map_count or equivalent for your platform.",
self.capacity, e
);
std::process::exit(1);
});
self.writer = Some(MmapAccountHashesFile {
mmap: map,
count: 0,
});
}
self.writer.as_mut().unwrap().write(hash);
}
}
@ -338,7 +352,8 @@ impl CumulativeHashesFromFiles {
let mut readers = Vec::with_capacity(hashes.len());
let cumulative = CumulativeOffsets::new(hashes.into_iter().filter_map(|mut hash_file| {
// ignores all hashfiles that have zero entries
hash_file.get_reader().map(|(count, reader)| {
hash_file.get_reader().map(|reader| {
let count = reader.count;
readers.push(reader);
count
})
@ -985,15 +1000,12 @@ impl<'a> AccountsHasher<'a> {
// map from index of an item in first_items[] to index of the corresponding item in sorted_data_by_pubkey[]
// this will change as items in sorted_data_by_pubkey[] are exhausted
let mut first_item_to_pubkey_division = Vec::with_capacity(len);
let mut hashes = AccountHashesFile {
count_and_writer: None,
dir_for_temp_cache_files: self.dir_for_temp_cache_files.clone(),
};
// initialize 'first_items', which holds the current lowest item in each slot group
sorted_data_by_pubkey
let max_inclusive_num_pubkeys = sorted_data_by_pubkey
.iter()
.enumerate()
.for_each(|(i, hash_data)| {
.map(|(i, hash_data)| {
let first_pubkey_in_bin =
Self::find_first_pubkey_in_bin(hash_data, pubkey_bin, bins, &binner, stats);
if let Some(first_pubkey_in_bin) = first_pubkey_in_bin {
@ -1001,8 +1013,27 @@ impl<'a> AccountsHasher<'a> {
first_items.push(k);
first_item_to_pubkey_division.push(i);
indexes.push(first_pubkey_in_bin);
let mut first_pubkey_in_next_bin = first_pubkey_in_bin + 1;
while first_pubkey_in_next_bin < hash_data.len() {
if binner.bin_from_pubkey(&hash_data[first_pubkey_in_next_bin].pubkey)
!= pubkey_bin
{
break;
}
first_pubkey_in_next_bin += 1;
}
first_pubkey_in_next_bin - first_pubkey_in_bin
} else {
0
}
});
})
.sum::<usize>();
let mut hashes = AccountHashesFile {
writer: None,
dir_for_temp_cache_files: self.dir_for_temp_cache_files.clone(),
capacity: max_inclusive_num_pubkeys * std::mem::size_of::<Hash>(),
};
let mut overall_sum = 0;
let mut duplicate_pubkey_indexes = Vec::with_capacity(len);
let filler_accounts_enabled = self.filler_accounts_enabled();
@ -1238,8 +1269,9 @@ pub mod tests {
impl AccountHashesFile {
fn new(dir_for_temp_cache_files: PathBuf) -> Self {
Self {
count_and_writer: None,
writer: None,
dir_for_temp_cache_files,
capacity: 1024, /* default 1k for tests */
}
}
}
@ -1308,16 +1340,16 @@ pub mod tests {
// 1 hash
file.write(&hashes[0]);
let reader = file.get_reader().unwrap();
assert_eq!(&[hashes[0]][..], reader.1.read(0));
assert!(reader.1.read(1).is_empty());
assert_eq!(&[hashes[0]][..], reader.read(0));
assert!(reader.read(1).is_empty());
// multiple hashes
let mut file = AccountHashesFile::new(dir_for_temp_cache_files.path().to_path_buf());
assert!(file.get_reader().is_none());
hashes.iter().for_each(|hash| file.write(hash));
let reader = file.get_reader().unwrap();
(0..2).for_each(|i| assert_eq!(&hashes[i..], reader.1.read(i)));
assert!(reader.1.read(2).is_empty());
(0..2).for_each(|i| assert_eq!(&hashes[i..], reader.read(i)));
assert!(reader.read(2).is_empty());
}
#[test]
@ -1476,7 +1508,7 @@ pub mod tests {
let accounts_hasher = AccountsHasher::new(dir_for_temp_cache_files.path().to_path_buf());
let (mut hashes, lamports) =
accounts_hasher.de_dup_accounts_in_parallel(&slice, 0, 1, &HashStats::default());
assert_eq!(&[Hash::default()], hashes.get_reader().unwrap().1.read(0));
assert_eq!(&[Hash::default()], hashes.get_reader().unwrap().read(0));
assert_eq!(lamports, 1);
}
@ -1486,7 +1518,7 @@ pub mod tests {
fn get_vec(mut hashes: AccountHashesFile) -> Vec<Hash> {
hashes
.get_reader()
.map(|r| r.1.read(0).to_vec())
.map(|r| r.read(0).to_vec())
.unwrap_or_default()
}