Use a background thread to load ISO into RAM.

This way we don't get slow startup.  This will also cache the CSO data,
for example, rather than the raw data, using up less RAM.  It might even
be reasonable to enable on 32-bit.
This commit is contained in:
Unknown W. Brackets 2015-12-19 15:23:25 -08:00
parent 4c0051dcfb
commit dd5c91108c
11 changed files with 331 additions and 52 deletions

View file

@ -1380,6 +1380,8 @@ add_library(${CoreLibName} ${CoreLinkType}
Core/FileLoaders/HTTPFileLoader.h
Core/FileLoaders/LocalFileLoader.cpp
Core/FileLoaders/LocalFileLoader.h
Core/FileLoaders/RamCachingFileLoader.cpp
Core/FileLoaders/RamCachingFileLoader.h
Core/FileLoaders/RetryingFileLoader.cpp
Core/FileLoaders/RetryingFileLoader.h
Core/MIPS/JitCommon/JitCommon.cpp

View file

@ -204,6 +204,7 @@
<ClCompile Include="FileLoaders\DiskCachingFileLoader.cpp" />
<ClCompile Include="FileLoaders\HTTPFileLoader.cpp" />
<ClCompile Include="FileLoaders\LocalFileLoader.cpp" />
<ClCompile Include="FileLoaders\RamCachingFileLoader.cpp" />
<ClCompile Include="FileLoaders\RetryingFileLoader.cpp" />
<ClCompile Include="FileSystems\BlockDevices.cpp" />
<ClCompile Include="FileSystems\DirectoryFileSystem.cpp" />
@ -531,6 +532,7 @@
<ClInclude Include="FileLoaders\DiskCachingFileLoader.h" />
<ClInclude Include="FileLoaders\HTTPFileLoader.h" />
<ClInclude Include="FileLoaders\LocalFileLoader.h" />
<ClInclude Include="FileLoaders\RamCachingFileLoader.h" />
<ClInclude Include="FileLoaders\RetryingFileLoader.h" />
<ClInclude Include="FileSystems\BlockDevices.h" />
<ClInclude Include="FileSystems\DirectoryFileSystem.h" />

View file

@ -628,6 +628,9 @@
<ClCompile Include="HW\SasReverb.cpp">
<Filter>HW</Filter>
</ClCompile>
<ClCompile Include="FileLoaders\RamCachingFileLoader.cpp">
<Filter>FileLoaders</Filter>
</ClCompile>
</ItemGroup>
<ItemGroup>
<ClInclude Include="ELF\ElfReader.h">
@ -1170,6 +1173,9 @@
<ClInclude Include="HW\SasReverb.h">
<Filter>HW</Filter>
</ClInclude>
<ClInclude Include="FileLoaders\RamCachingFileLoader.h">
<Filter>FileLoaders</Filter>
</ClInclude>
</ItemGroup>
<ItemGroup>
<None Include="CMakeLists.txt" />

View file

@ -0,0 +1,237 @@
// Copyright (c) 2015- PPSSPP Project.
// 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, version 2.0 or later versions.
// 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 2.0 for more details.
// A copy of the GPL 2.0 should have been included with the program.
// If not, see http://www.gnu.org/licenses/
// Official git repository and contact information can be found at
// https://github.com/hrydgard/ppsspp and http://www.ppsspp.org/.
#include <string.h>
#include "base/timeutil.h"
#include "thread/thread.h"
#include "thread/threadutil.h"
#include "Core/FileLoaders/RamCachingFileLoader.h"
#include "Common/Log.h"
// Takes ownership of backend.
RamCachingFileLoader::RamCachingFileLoader(FileLoader *backend)
: filesize_(0), filepos_(0), backend_(backend), exists_(-1), isDirectory_(-1), aheadThread_(false) {
filesize_ = backend->FileSize();
if (filesize_ > 0) {
InitCache();
}
}
RamCachingFileLoader::~RamCachingFileLoader() {
if (filesize_ > 0) {
ShutdownCache();
}
// Takes ownership.
delete backend_;
}
bool RamCachingFileLoader::Exists() {
if (exists_ == -1) {
lock_guard guard(backendMutex_);
exists_ = backend_->Exists() ? 1 : 0;
}
return exists_ == 1;
}
bool RamCachingFileLoader::IsDirectory() {
if (isDirectory_ == -1) {
lock_guard guard(backendMutex_);
isDirectory_ = backend_->IsDirectory() ? 1 : 0;
}
return isDirectory_ == 1;
}
s64 RamCachingFileLoader::FileSize() {
return filesize_;
}
std::string RamCachingFileLoader::Path() const {
lock_guard guard(backendMutex_);
return backend_->Path();
}
void RamCachingFileLoader::Seek(s64 absolutePos) {
filepos_ = absolutePos;
}
size_t RamCachingFileLoader::ReadAt(s64 absolutePos, size_t bytes, void *data) {
size_t readSize = ReadFromCache(absolutePos, bytes, data);
// While in case the cache size is too small for the entire read.
while (readSize < bytes) {
SaveIntoCache(absolutePos + readSize, bytes - readSize);
readSize += ReadFromCache(absolutePos + readSize, bytes - readSize, (u8 *)data + readSize);
}
StartReadAhead(absolutePos + readSize);
filepos_ = absolutePos + readSize;
return readSize;
}
void RamCachingFileLoader::InitCache() {
cache_ = new u8[filesize_];
lock_guard guard(blocksMutex_);
u32 blockCount = (u32)((filesize_ + BLOCK_SIZE - 1) >> BLOCK_SHIFT);
aheadRemaining_ = blockCount;
blocks_.resize(blockCount);
}
void RamCachingFileLoader::ShutdownCache() {
{
lock_guard guard(blocksMutex_);
// Try to have the thread stop.
aheadRemaining_ = 0;
}
// We can't delete while the thread is running, so have to wait.
// This should only happen from the menu.
while (aheadThread_) {
sleep_ms(1);
}
lock_guard guard(blocksMutex_);
blocks_.clear();
delete [] cache_;
cache_ = nullptr;
}
size_t RamCachingFileLoader::ReadFromCache(s64 pos, size_t bytes, void *data) {
s64 cacheStartPos = pos >> BLOCK_SHIFT;
s64 cacheEndPos = (pos + bytes - 1) >> BLOCK_SHIFT;
if ((size_t)cacheEndPos >= blocks_.size()) {
cacheEndPos = blocks_.size() - 1;
}
size_t readSize = 0;
size_t offset = (size_t)(pos - (cacheStartPos << BLOCK_SHIFT));
u8 *p = (u8 *)data;
lock_guard guard(blocksMutex_);
for (s64 i = cacheStartPos; i <= cacheEndPos; ++i) {
if (blocks_[i] == 0) {
return readSize;
}
size_t toRead = std::min(bytes - readSize, (size_t)BLOCK_SIZE - offset);
s64 cachePos = (i << BLOCK_SHIFT) + offset;
memcpy(p + readSize, &cache_[cachePos], toRead);
readSize += toRead;
// Don't need an offset after the first read.
offset = 0;
}
return readSize;
}
void RamCachingFileLoader::SaveIntoCache(s64 pos, size_t bytes) {
s64 cacheStartPos = pos >> BLOCK_SHIFT;
s64 cacheEndPos = (pos + bytes - 1) >> BLOCK_SHIFT;
if ((size_t)cacheEndPos >= blocks_.size()) {
cacheEndPos = blocks_.size() - 1;
}
size_t blocksToRead = 0;
{
lock_guard guard(blocksMutex_);
for (s64 i = cacheStartPos; i <= cacheEndPos; ++i) {
if (blocks_[i] == 0) {
++blocksToRead;
if (blocksToRead >= MAX_BLOCKS_PER_READ) {
break;
}
}
}
}
backendMutex_.lock();
s64 cacheFilePos = cacheStartPos << BLOCK_SHIFT;
backend_->ReadAt(cacheFilePos, blocksToRead << BLOCK_SHIFT, &cache_[cacheFilePos]);
backendMutex_.unlock();
{
lock_guard guard(blocksMutex_);
// In case they were simultaneously read.
u32 blocksRead = 0;
for (size_t i = 0; i < blocksToRead; ++i) {
if (blocks_[cacheStartPos + i] == 0) {
blocks_[cacheStartPos + i] = 1;
++blocksRead;
}
}
if (aheadRemaining_ != 0) {
aheadRemaining_ -= blocksRead;
}
}
}
void RamCachingFileLoader::StartReadAhead(s64 pos) {
lock_guard guard(blocksMutex_);
aheadPos_ = pos;
if (aheadThread_) {
// Already going.
return;
}
aheadThread_ = true;
std::thread th([this] {
setCurrentThreadName("FileLoaderReadAhead");
while (aheadRemaining_ != 0) {
// Where should we look?
const u32 cacheStartPos = NextAheadBlock();
if (cacheStartPos == 0xFFFFFFFF) {
// Must be full.
break;
}
u32 cacheEndPos = cacheStartPos + BLOCK_READAHEAD - 1;
if (cacheEndPos >= blocks_.size()) {
cacheEndPos = (u32)blocks_.size() - 1;
}
for (u32 i = cacheStartPos; i <= cacheEndPos; ++i) {
if (blocks_[i] == 0) {
SaveIntoCache(i << BLOCK_SHIFT, BLOCK_SIZE * BLOCK_READAHEAD);
break;
}
}
}
aheadThread_ = false;
});
th.detach();
}
u32 RamCachingFileLoader::NextAheadBlock() {
lock_guard guard(blocksMutex_);
// If we had an aheadPos_ set, start reading from there and go forward.
u32 startFrom = (u32)(aheadPos_ >> BLOCK_SHIFT);
// But next time, start from the beginning again.
aheadPos_ = 0;
for (u32 i = startFrom; i < blocks_.size(); ++i) {
if (blocks_[i] == 0) {
return i;
}
}
return 0xFFFFFFFF;
}

View file

@ -0,0 +1,76 @@
// Copyright (c) 2015- PPSSPP Project.
// 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, version 2.0 or later versions.
// 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 2.0 for more details.
// A copy of the GPL 2.0 should have been included with the program.
// If not, see http://www.gnu.org/licenses/
// Official git repository and contact information can be found at
// https://github.com/hrydgard/ppsspp and http://www.ppsspp.org/.
#pragma once
#include <vector>
#include "base/mutex.h"
#include "Common/CommonTypes.h"
#include "Core/Loaders.h"
class RamCachingFileLoader : public FileLoader {
public:
RamCachingFileLoader(FileLoader *backend);
~RamCachingFileLoader() override;
bool Exists() override;
bool IsDirectory() override;
s64 FileSize() override;
std::string Path() const override;
void Seek(s64 absolutePos) override;
size_t Read(size_t bytes, size_t count, void *data) override {
return ReadAt(filepos_, bytes, count, data);
}
size_t Read(size_t bytes, void *data) override {
return ReadAt(filepos_, bytes, data);
}
size_t ReadAt(s64 absolutePos, size_t bytes, size_t count, void *data) override {
return ReadAt(absolutePos, bytes * count, data) / bytes;
}
size_t ReadAt(s64 absolutePos, size_t bytes, void *data) override;
private:
void InitCache();
void ShutdownCache();
size_t ReadFromCache(s64 pos, size_t bytes, void *data);
// Guaranteed to read at least one block into the cache.
void SaveIntoCache(s64 pos, size_t bytes);
void StartReadAhead(s64 pos);
u32 NextAheadBlock();
enum {
BLOCK_SIZE = 65536,
BLOCK_SHIFT = 16,
MAX_BLOCKS_PER_READ = 16,
BLOCK_READAHEAD = 4,
};
s64 filesize_;
s64 filepos_;
FileLoader *backend_;
u8 *cache_;
int exists_;
int isDirectory_;
std::vector<u8> blocks_;
recursive_mutex blocksMutex_;
mutable recursive_mutex backendMutex_;
u32 aheadRemaining_;
s64 aheadPos_;
bool aheadThread_;
};

View file

@ -45,34 +45,6 @@ BlockDevice *constructBlockDevice(FileLoader *fileLoader) {
return new FileBlockDevice(fileLoader);
}
RAMBlockDevice::RAMBlockDevice(BlockDevice *device) {
totalBlocks_ = device->GetNumBlocks();
u32 blockSize = GetBlockSize();
image_ = new u8[totalBlocks_ * blockSize];
for (int i = 0; i < totalBlocks_; i++) {
device->ReadBlock(i, image_ + i * blockSize);
}
delete device;
}
RAMBlockDevice::~RAMBlockDevice() {
delete[] image_;
}
bool RAMBlockDevice::ReadBlock(int blockNumber, u8 *outPtr) {
if (blockNumber >= 0 && blockNumber < totalBlocks_) {
u32 blockSize = GetBlockSize();
memcpy(outPtr, image_ + blockSize * blockNumber, blockSize);
return true;
}
return false;
}
u32 RAMBlockDevice::GetNumBlocks() {
return totalBlocks_;
}
FileBlockDevice::FileBlockDevice(FileLoader *fileLoader)
: fileLoader_(fileLoader) {

View file

@ -122,21 +122,5 @@ private:
u8 *tempBuf;
};
// This simply fully reads another block device and caches it in RAM.
// A bit slow to initialize.
class RAMBlockDevice : public BlockDevice
{
public:
RAMBlockDevice(BlockDevice *device);
~RAMBlockDevice();
bool ReadBlock(int blockNumber, u8 *outPtr) override;
u32 GetNumBlocks() override;
private:
int totalBlocks_;
u8 *image_;
};
BlockDevice *constructBlockDevice(FileLoader *fileLoader);

View file

@ -73,13 +73,6 @@ void InitMemoryForGameISO(FileLoader *fileLoader) {
if (!bd)
return;
#ifdef _M_X64
if (g_Config.bCacheFullIsoInRam) {
// The constructor destroys the original block device object after reading it.
bd = new RAMBlockDevice(bd);
}
#endif
umd2 = new ISOFileSystem(&pspFileSystem, bd);
actualIso = true;
}

View file

@ -47,6 +47,7 @@
#include "Core/Core.h"
#include "Core/CoreTiming.h"
#include "Core/CoreParameter.h"
#include "Core/FileLoaders/RamCachingFileLoader.h"
#include "Core/FileSystems/MetaFileSystem.h"
#include "Core/Loaders.h"
#include "Core/PSPLoaders.h"
@ -187,6 +188,11 @@ void CPU_Init() {
std::string filename = coreParameter.fileToStart;
loadedFile = ConstructFileLoader(filename);
#ifdef _M_X64
if (g_Config.bCacheFullIsoInRam) {
loadedFile = new RamCachingFileLoader(loadedFile);
}
#endif
IdentifiedFileType type = Identify_File(loadedFile);
// TODO: Put this somewhere better?

View file

@ -582,7 +582,7 @@ void GameSettingsScreen::CreateViews() {
#endif
#if defined(_M_X64)
systemSettings->Add(new CheckBox(&g_Config.bCacheFullIsoInRam, sy->T("Cache ISO in RAM", "Cache full ISO in RAM (slow startup)")));
systemSettings->Add(new CheckBox(&g_Config.bCacheFullIsoInRam, sy->T("Cache ISO in RAM", "Cache full ISO in RAM")));
#endif
//#ifndef ANDROID

View file

@ -220,6 +220,7 @@ EXEC_AND_LIB_FILES := \
$(SRC)/Core/FileLoaders/DiskCachingFileLoader.cpp \
$(SRC)/Core/FileLoaders/HTTPFileLoader.cpp \
$(SRC)/Core/FileLoaders/LocalFileLoader.cpp \
$(SRC)/Core/FileLoaders/RamCachingFileLoader.cpp \
$(SRC)/Core/FileLoaders/RetryingFileLoader.cpp \
$(SRC)/Core/MemMap.cpp \
$(SRC)/Core/MemMapFunctions.cpp \