libsnapshot:snapuserd: Cut down memory usage

Use sorted std:vector instead of std:map to store
the mapping between chunk-id to COW operation.

Addtionally, use shrink_to_fit to cut down vector
capacity when COW operations are stored.

On a full OTA of 1.8G, Anon RSS usage is
reduced from 120MB to 68MB.  No variance observed
when merge was in progress.

Bug: 182960300
Test: Full and Incremental OTA - verified memory usage
Signed-off-by: Akilesh Kailash <akailash@google.com>
Change-Id: I50cacbe0d03837a830dedcf9bd0ac9663fc68fa7
This commit is contained in:
Akilesh Kailash 2021-03-18 08:18:22 +00:00
parent 49699fe895
commit 3e807e9651
4 changed files with 56 additions and 29 deletions

View file

@ -250,6 +250,8 @@ bool CowReader::ParseOps(std::optional<uint64_t> label) {
}
ops_ = ops_buffer;
ops_->shrink_to_fit();
return true;
}

View file

@ -230,7 +230,7 @@ bool Snapuserd::ReadMetadata() {
// Store operation pointer.
chunk_map_[ChunkToSector(data_chunk_id)] = cow_op;
chunk_vec_.push_back(std::make_pair(ChunkToSector(data_chunk_id), cow_op));
num_ops += 1;
offset += sizeof(struct disk_exception);
cowop_riter_->Next();
@ -422,7 +422,7 @@ bool Snapuserd::ReadMetadata() {
de->new_chunk = data_chunk_id;
// Store operation pointer.
chunk_map_[ChunkToSector(data_chunk_id)] = it->second;
chunk_vec_.push_back(std::make_pair(ChunkToSector(data_chunk_id), it->second));
offset += sizeof(struct disk_exception);
num_ops += 1;
copy_ops++;
@ -468,6 +468,12 @@ bool Snapuserd::ReadMetadata() {
<< "Areas : " << vec_.size();
}
chunk_vec_.shrink_to_fit();
vec_.shrink_to_fit();
// Sort the vector based on sectors as we need this during un-aligned access
std::sort(chunk_vec_.begin(), chunk_vec_.end(), compare);
SNAP_LOG(INFO) << "ReadMetadata completed. Final-chunk-id: " << data_chunk_id
<< " Num Sector: " << ChunkToSector(data_chunk_id)
<< " Replace-ops: " << replace_ops << " Zero-ops: " << zero_ops

View file

@ -108,7 +108,7 @@ class WorkerThread {
bool ProcessIORequest();
int ReadData(sector_t sector, size_t size);
int ReadUnalignedSector(sector_t sector, size_t size,
std::map<sector_t, const CowOperation*>::iterator& it);
std::vector<std::pair<sector_t, const CowOperation*>>::iterator& it);
// Processing COW operations
bool ProcessCowOp(const CowOperation* cow_op);
@ -164,16 +164,21 @@ class Snapuserd : public std::enable_shared_from_this<Snapuserd> {
bool InitializeWorkers();
std::shared_ptr<Snapuserd> GetSharedPtr() { return shared_from_this(); }
std::map<sector_t, const CowOperation*>& GetChunkMap() { return chunk_map_; }
std::vector<std::pair<sector_t, const CowOperation*>>& GetChunkVec() { return chunk_vec_; }
const std::vector<std::unique_ptr<uint8_t[]>>& GetMetadataVec() const { return vec_; }
static bool compare(std::pair<sector_t, const CowOperation*> p1,
std::pair<sector_t, const CowOperation*> p2) {
return p1.first < p2.first;
}
private:
std::vector<std::unique_ptr<WorkerThread>> worker_threads_;
bool ReadMetadata();
bool IsChunkIdMetadata(chunk_t chunk);
chunk_t GetNextAllocatableChunkId(chunk_t chunk_id);
bool ReadMetadata();
sector_t ChunkToSector(chunk_t chunk) { return chunk << CHUNK_SHIFT; }
chunk_t SectorToChunk(sector_t sector) { return sector >> CHUNK_SHIFT; }
bool IsBlockAligned(int read_size) { return ((read_size & (BLOCK_SZ - 1)) == 0); }
@ -197,15 +202,9 @@ class Snapuserd : public std::enable_shared_from_this<Snapuserd> {
// mapping of old-chunk to new-chunk
std::vector<std::unique_ptr<uint8_t[]>> vec_;
// Key - Sector
// Value - cow operation
//
// chunk_map stores the pseudo mapping of sector
// to COW operations. Each COW op is 4k; however,
// we can get a read request which are as small
// as 512 bytes. Hence, we need to binary search
// in the chunk_map to find the nearest COW op.
std::map<sector_t, const CowOperation*> chunk_map_;
// chunk_vec stores the pseudo mapping of sector
// to COW operations.
std::vector<std::pair<sector_t, const CowOperation*>> chunk_vec_;
std::mutex lock_;

View file

@ -185,12 +185,13 @@ bool WorkerThread::ProcessCowOp(const CowOperation* cow_op) {
return false;
}
int WorkerThread::ReadUnalignedSector(sector_t sector, size_t size,
std::map<sector_t, const CowOperation*>::iterator& it) {
int WorkerThread::ReadUnalignedSector(
sector_t sector, size_t size,
std::vector<std::pair<sector_t, const CowOperation*>>::iterator& it) {
size_t skip_sector_size = 0;
SNAP_LOG(DEBUG) << "ReadUnalignedSector: sector " << sector << " size: " << size
<< " Aligned sector: " << it->second;
<< " Aligned sector: " << it->first;
if (!ProcessCowOp(it->second)) {
SNAP_LOG(ERROR) << "ReadUnalignedSector: " << sector << " failed of size: " << size;
@ -223,7 +224,8 @@ int WorkerThread::ReadUnalignedSector(sector_t sector, size_t size,
*
*/
int WorkerThread::ReadData(sector_t sector, size_t size) {
std::map<sector_t, const CowOperation*>& chunk_map = snapuserd_->GetChunkMap();
std::vector<std::pair<sector_t, const CowOperation*>>& chunk_vec = snapuserd_->GetChunkVec();
std::vector<std::pair<sector_t, const CowOperation*>>::iterator it;
/*
* chunk_map stores COW operation at 4k granularity.
* If the requested IO with the sector falls on the 4k
@ -234,10 +236,16 @@ int WorkerThread::ReadData(sector_t sector, size_t size) {
* then we will have the find the nearest COW operation
* and chop the 4K block to fetch the requested sector.
*/
std::map<sector_t, const CowOperation*>::iterator it = chunk_map.find(sector);
if (it == chunk_map.end()) {
it = chunk_map.lower_bound(sector);
if (it != chunk_map.begin()) {
it = std::lower_bound(chunk_vec.begin(), chunk_vec.end(), std::make_pair(sector, nullptr),
Snapuserd::compare);
CHECK(it != chunk_vec.end());
// We didn't find the required sector; hence find the previous sector
// as lower_bound will gives us the value greater than
// the requested sector
if (it->first != sector) {
if (it != chunk_vec.begin()) {
--it;
}
@ -380,7 +388,7 @@ loff_t WorkerThread::GetMergeStartOffset(void* merged_buffer, void* unmerged_buf
int WorkerThread::GetNumberOfMergedOps(void* merged_buffer, void* unmerged_buffer, loff_t offset,
int unmerged_exceptions) {
int merged_ops_cur_iter = 0;
std::map<sector_t, const CowOperation*>& chunk_map = snapuserd_->GetChunkMap();
std::vector<std::pair<sector_t, const CowOperation*>>& chunk_vec = snapuserd_->GetChunkVec();
// Find the operations which are merged in this cycle.
while ((unmerged_exceptions + merged_ops_cur_iter) < exceptions_per_area_) {
@ -395,7 +403,13 @@ int WorkerThread::GetNumberOfMergedOps(void* merged_buffer, void* unmerged_buffe
if (cow_de->new_chunk != 0) {
merged_ops_cur_iter += 1;
offset += sizeof(struct disk_exception);
const CowOperation* cow_op = chunk_map[ChunkToSector(cow_de->new_chunk)];
auto it = std::lower_bound(chunk_vec.begin(), chunk_vec.end(),
std::make_pair(ChunkToSector(cow_de->new_chunk), nullptr),
Snapuserd::compare);
CHECK(it != chunk_vec.end());
CHECK(it->first == ChunkToSector(cow_de->new_chunk));
const CowOperation* cow_op = it->second;
CHECK(cow_op != nullptr);
CHECK(cow_op->new_block == cow_de->old_chunk);
@ -510,14 +524,18 @@ bool WorkerThread::DmuserWriteRequest() {
return true;
}
std::map<sector_t, const CowOperation*>& chunk_map = snapuserd_->GetChunkMap();
std::vector<std::pair<sector_t, const CowOperation*>>& chunk_vec = snapuserd_->GetChunkVec();
size_t remaining_size = header->len;
size_t read_size = std::min(PAYLOAD_SIZE, remaining_size);
CHECK(read_size == BLOCK_SZ) << "DmuserWriteRequest: read_size: " << read_size;
CHECK(header->sector > 0);
chunk_t chunk = SectorToChunk(header->sector);
CHECK(chunk_map.find(header->sector) == chunk_map.end());
auto it = std::lower_bound(chunk_vec.begin(), chunk_vec.end(),
std::make_pair(header->sector, nullptr), Snapuserd::compare);
bool not_found = (it == chunk_vec.end() || it->first != header->sector);
CHECK(not_found);
void* buffer = bufsink_.GetPayloadBuffer(read_size);
CHECK(buffer != nullptr);
@ -550,7 +568,7 @@ bool WorkerThread::DmuserReadRequest() {
size_t remaining_size = header->len;
loff_t offset = 0;
sector_t sector = header->sector;
std::map<sector_t, const CowOperation*>& chunk_map = snapuserd_->GetChunkMap();
std::vector<std::pair<sector_t, const CowOperation*>>& chunk_vec = snapuserd_->GetChunkVec();
do {
size_t read_size = std::min(PAYLOAD_SIZE, remaining_size);
@ -568,8 +586,10 @@ bool WorkerThread::DmuserReadRequest() {
ConstructKernelCowHeader();
SNAP_LOG(DEBUG) << "Kernel header constructed";
} else {
if (!offset && (read_size == BLOCK_SZ) &&
chunk_map.find(header->sector) == chunk_map.end()) {
auto it = std::lower_bound(chunk_vec.begin(), chunk_vec.end(),
std::make_pair(header->sector, nullptr), Snapuserd::compare);
bool not_found = (it == chunk_vec.end() || it->first != header->sector);
if (!offset && (read_size == BLOCK_SZ) && not_found) {
if (!ReadDiskExceptions(chunk, read_size)) {
SNAP_LOG(ERROR) << "ReadDiskExceptions failed for chunk id: " << chunk
<< "Sector: " << header->sector;