Implement RomFS cache and async reads. (#7089)
* Implement RomFS cache and async reads. * Suggestions and fix compilation. * Apply suggestions
This commit is contained in:
parent
79ea06b226
commit
4284893044
10 changed files with 404 additions and 22 deletions
|
@ -86,6 +86,20 @@ public:
|
|||
*/
|
||||
virtual void Flush() const = 0;
|
||||
|
||||
/**
|
||||
* Whether the backend supports cached reads.
|
||||
*/
|
||||
virtual bool AllowsCachedReads() const {
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether the cache is ready for a specified offset and length.
|
||||
*/
|
||||
virtual bool CacheReady(std::size_t file_offset, std::size_t length) {
|
||||
return false;
|
||||
}
|
||||
|
||||
protected:
|
||||
std::unique_ptr<DelayGenerator> delay_generator;
|
||||
|
||||
|
|
|
@ -131,6 +131,14 @@ public:
|
|||
}
|
||||
void Flush() const override {}
|
||||
|
||||
bool AllowsCachedReads() const override {
|
||||
return romfs_file->AllowsCachedReads();
|
||||
}
|
||||
|
||||
bool CacheReady(std::size_t file_offset, std::size_t length) override {
|
||||
return romfs_file->CacheReady(file_offset, length);
|
||||
}
|
||||
|
||||
private:
|
||||
std::shared_ptr<RomFSReader> romfs_file;
|
||||
|
||||
|
|
|
@ -53,6 +53,14 @@ public:
|
|||
|
||||
bool DumpRomFS(const std::string& target_path);
|
||||
|
||||
bool AllowsCachedReads() const override {
|
||||
return false;
|
||||
}
|
||||
|
||||
bool CacheReady(std::size_t file_offset, std::size_t length) override {
|
||||
return false;
|
||||
}
|
||||
|
||||
private:
|
||||
struct File;
|
||||
struct Directory {
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
#include <algorithm>
|
||||
#include <vector>
|
||||
#include <cryptopp/aes.h>
|
||||
#include <cryptopp/modes.h>
|
||||
#include "common/archives.h"
|
||||
|
@ -9,17 +10,102 @@ SERIALIZE_EXPORT_IMPL(FileSys::DirectRomFSReader)
|
|||
namespace FileSys {
|
||||
|
||||
std::size_t DirectRomFSReader::ReadFile(std::size_t offset, std::size_t length, u8* buffer) {
|
||||
length = std::min(length, static_cast<std::size_t>(data_size) - offset);
|
||||
if (length == 0)
|
||||
return 0; // Crypto++ does not like zero size buffer
|
||||
file.Seek(file_offset + offset, SEEK_SET);
|
||||
std::size_t read_length = std::min(length, static_cast<std::size_t>(data_size) - offset);
|
||||
read_length = file.ReadBytes(buffer, read_length);
|
||||
if (is_encrypted) {
|
||||
CryptoPP::CTR_Mode<CryptoPP::AES>::Decryption d(key.data(), key.size(), ctr.data());
|
||||
d.Seek(crypto_offset + offset);
|
||||
d.ProcessData(buffer, buffer, read_length);
|
||||
|
||||
const auto segments = BreakupRead(offset, length);
|
||||
size_t read_progress = 0;
|
||||
|
||||
// Skip cache if the read is too big
|
||||
if (segments.size() == 1 && segments[0].second > cache_line_size) {
|
||||
length = file.ReadAtBytes(buffer, length, file_offset + offset);
|
||||
if (is_encrypted) {
|
||||
CryptoPP::CTR_Mode<CryptoPP::AES>::Decryption d(key.data(), key.size(), ctr.data());
|
||||
d.Seek(crypto_offset + offset);
|
||||
d.ProcessData(buffer, buffer, length);
|
||||
}
|
||||
// LOG_INFO(Service_FS, "Cache SKIP: offset={}, length={}", offset, length);
|
||||
return length;
|
||||
}
|
||||
return read_length;
|
||||
|
||||
// TODO(PabloMK7): Make cache thread safe, read the comment in CacheReady function.
|
||||
// std::unique_lock<std::shared_mutex> read_guard(cache_mutex);
|
||||
for (const auto& seg : segments) {
|
||||
size_t read_size = cache_line_size;
|
||||
size_t page = OffsetToPage(seg.first);
|
||||
// Check if segment is in cache
|
||||
auto cache_entry = cache.request(page);
|
||||
if (!cache_entry.first) {
|
||||
// If not found, read from disk and cache the data
|
||||
read_size = file.ReadAtBytes(cache_entry.second.data(), read_size, file_offset + page);
|
||||
if (is_encrypted && read_size) {
|
||||
CryptoPP::CTR_Mode<CryptoPP::AES>::Decryption d(key.data(), key.size(), ctr.data());
|
||||
d.Seek(crypto_offset + page);
|
||||
d.ProcessData(cache_entry.second.data(), cache_entry.second.data(), read_size);
|
||||
}
|
||||
// LOG_INFO(Service_FS, "Cache MISS: page={}, length={}, into={}", page, seg.second,
|
||||
// (seg.first - page));
|
||||
} else {
|
||||
// LOG_INFO(Service_FS, "Cache HIT: page={}, length={}, into={}", page, seg.second,
|
||||
// (seg.first - page));
|
||||
}
|
||||
size_t copy_amount =
|
||||
(read_size > (seg.first - page))
|
||||
? std::min((seg.first - page) + seg.second, read_size) - (seg.first - page)
|
||||
: 0;
|
||||
std::memcpy(buffer + read_progress, cache_entry.second.data() + (seg.first - page),
|
||||
copy_amount);
|
||||
read_progress += copy_amount;
|
||||
}
|
||||
return read_progress;
|
||||
}
|
||||
|
||||
bool DirectRomFSReader::AllowsCachedReads() const {
|
||||
return true;
|
||||
}
|
||||
|
||||
bool DirectRomFSReader::CacheReady(std::size_t file_offset, std::size_t length) {
|
||||
auto segments = BreakupRead(file_offset, length);
|
||||
if (segments.size() == 1 && segments[0].second > cache_line_size) {
|
||||
return false;
|
||||
} else {
|
||||
// TODO(PabloMK7): Since the LRU cache is not thread safe, a lock must be used.
|
||||
// However, this completely breaks the point of using a cache, because
|
||||
// smaller reads may be blocked by bigger reads. For now, always return
|
||||
// data being in cache to prevent the need of a lock, and only read data
|
||||
// asynchronously if it is too big to use the cache.
|
||||
/*
|
||||
std::shared_lock<std::shared_mutex> read_guard(cache_mutex);
|
||||
for (auto it = segments.begin(); it != segments.end(); it++) {
|
||||
if (!cache.contains(OffsetToPage(it->first)))
|
||||
return false;
|
||||
}
|
||||
*/
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
std::vector<std::pair<std::size_t, std::size_t>> DirectRomFSReader::BreakupRead(
|
||||
std::size_t offset, std::size_t length) {
|
||||
|
||||
std::vector<std::pair<std::size_t, std::size_t>> ret;
|
||||
|
||||
// Reads bigger than the cache line size will probably never hit again
|
||||
if (length > cache_line_size) {
|
||||
ret.push_back(std::make_pair(offset, length));
|
||||
return ret;
|
||||
}
|
||||
|
||||
size_t curr_offset = offset;
|
||||
while (length) {
|
||||
size_t next_page = OffsetToPage(curr_offset + cache_line_size);
|
||||
size_t curr_page_len = std::min(length, next_page - curr_offset);
|
||||
ret.push_back(std::make_pair(curr_offset, curr_page_len));
|
||||
curr_offset = next_page;
|
||||
length -= curr_page_len;
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
} // namespace FileSys
|
||||
|
|
|
@ -1,11 +1,14 @@
|
|||
#pragma once
|
||||
|
||||
#include <array>
|
||||
#include <shared_mutex>
|
||||
#include <boost/serialization/array.hpp>
|
||||
#include <boost/serialization/base_object.hpp>
|
||||
#include <boost/serialization/export.hpp>
|
||||
#include "common/alignment.h"
|
||||
#include "common/common_types.h"
|
||||
#include "common/file_util.h"
|
||||
#include "common/static_lru_cache.h"
|
||||
|
||||
namespace FileSys {
|
||||
|
||||
|
@ -18,6 +21,8 @@ public:
|
|||
|
||||
virtual std::size_t GetSize() const = 0;
|
||||
virtual std::size_t ReadFile(std::size_t offset, std::size_t length, u8* buffer) = 0;
|
||||
virtual bool AllowsCachedReads() const = 0;
|
||||
virtual bool CacheReady(std::size_t file_offset, std::size_t length) = 0;
|
||||
|
||||
private:
|
||||
template <class Archive>
|
||||
|
@ -48,6 +53,10 @@ public:
|
|||
|
||||
std::size_t ReadFile(std::size_t offset, std::size_t length, u8* buffer) override;
|
||||
|
||||
bool AllowsCachedReads() const override;
|
||||
|
||||
bool CacheReady(std::size_t file_offset, std::size_t length) override;
|
||||
|
||||
private:
|
||||
bool is_encrypted;
|
||||
FileUtil::IOFile file;
|
||||
|
@ -57,8 +66,23 @@ private:
|
|||
u64 crypto_offset;
|
||||
u64 data_size;
|
||||
|
||||
// Total cache size: 128KB
|
||||
static constexpr size_t cache_line_size = (1 << 13); // About 8KB
|
||||
static constexpr size_t cache_line_count = 16;
|
||||
|
||||
Common::StaticLRUCache<std::size_t, std::array<u8, cache_line_size>, cache_line_count> cache;
|
||||
// TODO(PabloMK7): Make cache thread safe, read the comment in CacheReady function.
|
||||
// std::shared_mutex cache_mutex;
|
||||
|
||||
DirectRomFSReader() = default;
|
||||
|
||||
std::size_t OffsetToPage(std::size_t offset) {
|
||||
return Common::AlignDown<std::size_t>(offset, cache_line_size);
|
||||
}
|
||||
|
||||
std::vector<std::pair<std::size_t, std::size_t>> BreakupRead(std::size_t offset,
|
||||
std::size_t length);
|
||||
|
||||
template <class Archive>
|
||||
void serialize(Archive& ar, const unsigned int) {
|
||||
ar& boost::serialization::base_object<RomFSReader>(*this);
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue