mirror of https://github.com/mpv-player/mpv
vo_gpu_next: save cache to separate files
Save the cache to separate files to avoid loading/saving a huge combined libplacebo.cache. This approach allows the saving of only new cache objects and avoids resaving the entire cache, especially even if only a tiny change was made. This commit improves the cold start time of mpv and avoids saving data when it's not necessary. Number of changes were made: - each cached object is saved in its own file - cache files are prefixed with the name of cached object - cache directory is cleaned on each uninit - the least recently used cache files are removed if cumulative cache size is above limit - files used in the recent 24 hours are not removed to allow changes to mpv.conf without worrying about the cache being removed during experimentation - shader cache size limit is set to 128 MiB - icc cache size limit is set to 1.5 GiB - cache objects are loaded/saved as needed This commit eliminates the runtime performance penalty associated with the size cache. While we continue to maintain the cache limit to prevent retaining stale objects, mpv now only loads a small subset of files that are currently required for playback, instead of loading all files.
This commit is contained in:
parent
a59b8edb96
commit
69891c4070
|
@ -6869,8 +6869,11 @@ them.
|
|||
files contain uncompressed LUTs. Their size depends on the
|
||||
``--icc-3dlut-size``, and can be very big.
|
||||
|
||||
NOTE: On ``--vo=gpu``, this is not cleaned automatically, so old, unused
|
||||
cache files may stick around indefinitely.
|
||||
On `--vo=gpu-next`, files that have not been accessed in the last 24 hours
|
||||
may be cleared if the cache limit (1.5 GiB) is exceeded.
|
||||
|
||||
On ``--vo=gpu``, this is not cleaned automatically, so old, unused cache
|
||||
files may stick around indefinitely.
|
||||
|
||||
``--icc-cache-dir``
|
||||
The directory where icc cache is stored. Cache is stored in the system's
|
||||
|
@ -7018,11 +7021,14 @@ them.
|
|||
``--gpu-shader-cache``
|
||||
Store and load compiled GLSL shaders in the cache directory (Default:
|
||||
``yes``). Normally, shader compilation is very fast, so this is not usually
|
||||
needed. It mostly matters for anything based on D3D11 (including ANGLE), as
|
||||
well as on some other proprietary drivers. Enabling this can improve startup
|
||||
performance on these platforms.
|
||||
needed. It mostly matters for anything involving GLSL to SPIR-V conversion,
|
||||
that is: D3D11, ANGLE or Vulkan, as well as on some other proprietary
|
||||
drivers. Enabling this can improve startup performance on these platforms.
|
||||
|
||||
NOTE: On ``--vo=gpu``, is not cleaned automatically, so old, unused cache
|
||||
On `--vo=gpu-next`, files that have not been accessed in the last 24 hours
|
||||
may be cleared if the cache limit (128 MiB) is exceeded.
|
||||
|
||||
On ``--vo=gpu``, this is not cleaned automatically, so old, unused cache
|
||||
files may stick around indefinitely.
|
||||
|
||||
``--gpu-shader-cache-dir``
|
||||
|
|
|
@ -17,6 +17,9 @@
|
|||
* License along with mpv. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include <dirent.h>
|
||||
#include <sys/stat.h>
|
||||
#include <time.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include <libplacebo/colorspace.h>
|
||||
|
@ -90,9 +93,12 @@ struct frame_info {
|
|||
};
|
||||
|
||||
struct cache {
|
||||
char *path;
|
||||
struct mp_log *log;
|
||||
struct mpv_global *global;
|
||||
char *dir;
|
||||
const char *name;
|
||||
size_t size_limit;
|
||||
pl_cache cache;
|
||||
uint64_t sig;
|
||||
};
|
||||
|
||||
struct priv {
|
||||
|
@ -1499,84 +1505,211 @@ static void wait_events(struct vo *vo, int64_t until_time_ns)
|
|||
}
|
||||
}
|
||||
|
||||
#if PL_API_VER < 342
|
||||
static inline void xor_hash(void *hash, pl_cache_obj obj)
|
||||
static char *cache_filepath(void *ta_ctx, char *dir, const char *prefix, uint64_t key)
|
||||
{
|
||||
*((uint64_t *) hash) ^= obj.key;
|
||||
bstr filename = {0};
|
||||
bstr_xappend_asprintf(ta_ctx, &filename, "%s_%016" PRIx64, prefix, key);
|
||||
return mp_path_join_bstr(ta_ctx, bstr0(dir), filename);
|
||||
}
|
||||
|
||||
static inline uint64_t pl_cache_signature(pl_cache cache)
|
||||
static void cache_save_file(void *ta_ctx, char *filepath, void *data, size_t size)
|
||||
{
|
||||
uint64_t hash = 0;
|
||||
pl_cache_iterate(cache, xor_hash, &hash);
|
||||
return hash;
|
||||
if (!data || !size)
|
||||
return;
|
||||
|
||||
char *tmp = talloc_asprintf(ta_ctx, "%sXXXXXX", filepath);
|
||||
int fd = mkstemp(tmp);
|
||||
if (fd < 0)
|
||||
return;
|
||||
FILE *cache = fdopen(fd, "wb");
|
||||
if (!cache) {
|
||||
close(fd);
|
||||
unlink(tmp);
|
||||
return;
|
||||
}
|
||||
size_t written = fwrite(data, size, 1, cache);
|
||||
int ret = fclose(cache);
|
||||
if (written > 0 && !ret) {
|
||||
ret = rename(tmp, filepath);
|
||||
} else {
|
||||
unlink(tmp);
|
||||
}
|
||||
}
|
||||
|
||||
static pl_cache_obj cache_load_obj(void *p, uint64_t key)
|
||||
{
|
||||
struct cache *c = p;
|
||||
void *ta_ctx = talloc_new(NULL);
|
||||
pl_cache_obj obj = {0};
|
||||
|
||||
if (!c->dir)
|
||||
goto done;
|
||||
|
||||
char *filepath = cache_filepath(ta_ctx, c->dir, c->name, key);
|
||||
if (!filepath)
|
||||
goto done;
|
||||
|
||||
if (stat(filepath, &(struct stat){0}))
|
||||
goto done;
|
||||
|
||||
int64_t load_start = mp_time_ns();
|
||||
struct bstr data = stream_read_file(filepath, ta_ctx, c->global, STREAM_MAX_READ_SIZE);
|
||||
int64_t load_end = mp_time_ns();
|
||||
MP_DBG(c, "%s: key(%" PRIx64 "), size(%zu), load time(%.3f ms)\n",
|
||||
__func__, key, data.len,
|
||||
MP_TIME_NS_TO_MS(load_end - load_start));
|
||||
|
||||
obj = (pl_cache_obj){
|
||||
.key = key,
|
||||
.data = talloc_steal(NULL, data.start),
|
||||
.size = data.len,
|
||||
.free = talloc_free,
|
||||
};
|
||||
|
||||
done:
|
||||
talloc_free(ta_ctx);
|
||||
return obj;
|
||||
}
|
||||
|
||||
static void cache_save_obj(void *p, pl_cache_obj obj)
|
||||
{
|
||||
const struct cache *c = p;
|
||||
void *ta_ctx = talloc_new(NULL);
|
||||
|
||||
if (!c->dir)
|
||||
goto done;
|
||||
|
||||
char *filepath = cache_filepath(ta_ctx, c->dir, c->name, obj.key);
|
||||
if (!filepath)
|
||||
goto done;
|
||||
|
||||
// Don't save if already exists
|
||||
if (!stat(filepath, &(struct stat){0})) {
|
||||
MP_DBG(c, "%s: key(%"PRIx64"), size(%zu)\n", __func__, obj.key, obj.size);
|
||||
goto done;
|
||||
}
|
||||
|
||||
int64_t save_start = mp_time_ns();
|
||||
cache_save_file(ta_ctx, filepath, obj.data, obj.size);
|
||||
int64_t save_end = mp_time_ns();
|
||||
MP_DBG(c, "%s: key(%" PRIx64 "), size(%zu), save time(%.3f ms)\n",
|
||||
__func__, obj.key, obj.size,
|
||||
MP_TIME_NS_TO_MS(save_end - save_start));
|
||||
|
||||
done:
|
||||
talloc_free(ta_ctx);
|
||||
}
|
||||
#endif
|
||||
|
||||
static void cache_init(struct vo *vo, struct cache *cache, size_t max_size,
|
||||
const char *dir_opt)
|
||||
{
|
||||
struct priv *p = vo->priv;
|
||||
const char *name = cache == &p->shader_cache ? "shader.cache" : "icc.cache";
|
||||
const char *name = cache == &p->shader_cache ? "shader" : "icc";
|
||||
const size_t limit = cache == &p->shader_cache ? 128 << 20 : 1536 << 20;
|
||||
|
||||
char *dir;
|
||||
if (dir_opt && dir_opt[0]) {
|
||||
dir = mp_get_user_path(NULL, p->global, dir_opt);
|
||||
dir = mp_get_user_path(vo, p->global, dir_opt);
|
||||
} else {
|
||||
dir = mp_find_user_file(NULL, p->global, "cache", "");
|
||||
dir = mp_find_user_file(vo, p->global, "cache", "");
|
||||
}
|
||||
if (!dir || !dir[0])
|
||||
goto done;
|
||||
return;
|
||||
|
||||
mp_mkdirp(dir);
|
||||
cache->path = mp_path_join(vo, dir, name);
|
||||
cache->cache = pl_cache_create(pl_cache_params(
|
||||
*cache = (struct cache){
|
||||
.log = p->log,
|
||||
.global = p->global,
|
||||
.dir = dir,
|
||||
.name = name,
|
||||
.size_limit = limit,
|
||||
.cache = pl_cache_create(pl_cache_params(
|
||||
.log = p->pllog,
|
||||
.max_total_size = max_size,
|
||||
));
|
||||
|
||||
FILE *file = fopen(cache->path, "rb");
|
||||
if (file) {
|
||||
int ret = pl_cache_load_file(cache->cache, file);
|
||||
fclose(file);
|
||||
if (ret < 0)
|
||||
MP_WARN(p, "Failed loading cache from %s\n", cache->path);
|
||||
.get = cache_load_obj,
|
||||
.set = cache_save_obj,
|
||||
.priv = cache
|
||||
)),
|
||||
};
|
||||
}
|
||||
|
||||
cache->sig = pl_cache_signature(cache->cache);
|
||||
done:
|
||||
talloc_free(dir);
|
||||
struct file_entry {
|
||||
char *filepath;
|
||||
size_t size;
|
||||
time_t atime;
|
||||
};
|
||||
|
||||
static int compare_atime(const void *a, const void *b)
|
||||
{
|
||||
return (((struct file_entry *)b)->atime - ((struct file_entry *)a)->atime);
|
||||
}
|
||||
|
||||
static void cache_uninit(struct priv *p, struct cache *cache)
|
||||
{
|
||||
if (!cache->cache)
|
||||
goto done;
|
||||
if (pl_cache_signature(cache->cache) == cache->sig)
|
||||
goto done; // skip re-saving identical cache
|
||||
return;
|
||||
|
||||
assert(cache->path);
|
||||
char *tmp = talloc_asprintf(cache->path, "%sXXXXXX", cache->path);
|
||||
int fd = mkstemp(tmp);
|
||||
if (fd < 0)
|
||||
goto done;
|
||||
FILE *file = fdopen(fd, "wb");
|
||||
if (!file) {
|
||||
close(fd);
|
||||
unlink(tmp);
|
||||
void *ta_ctx = talloc_new(NULL);
|
||||
struct file_entry *files = NULL;
|
||||
size_t num_files = 0;
|
||||
assert(cache->dir);
|
||||
assert(cache->name);
|
||||
|
||||
DIR *d = opendir(cache->dir);
|
||||
if (!d)
|
||||
goto done;
|
||||
|
||||
struct dirent *dir;
|
||||
while ((dir = readdir(d)) != NULL) {
|
||||
char *filepath = mp_path_join(ta_ctx, cache->dir, dir->d_name);
|
||||
if (!filepath)
|
||||
continue;
|
||||
struct stat filestat;
|
||||
if (stat(filepath, &filestat))
|
||||
continue;
|
||||
if (!S_ISREG(filestat.st_mode))
|
||||
continue;
|
||||
bstr fname = bstr0(dir->d_name);
|
||||
if (!bstr_eatstart0(&fname, cache->name))
|
||||
continue;
|
||||
if (!bstr_eatstart0(&fname, "_"))
|
||||
continue;
|
||||
if (fname.len != 16) // %016x
|
||||
continue;
|
||||
MP_TARRAY_APPEND(ta_ctx, files, num_files,
|
||||
(struct file_entry){
|
||||
.filepath = filepath,
|
||||
.size = filestat.st_size,
|
||||
.atime = filestat.st_atime,
|
||||
});
|
||||
}
|
||||
closedir(d);
|
||||
|
||||
if (!num_files)
|
||||
goto done;
|
||||
|
||||
qsort(files, num_files, sizeof(struct file_entry), compare_atime);
|
||||
|
||||
time_t t = time(NULL);
|
||||
size_t cache_size = 0;
|
||||
size_t cache_limit = cache->size_limit ? cache->size_limit : SIZE_MAX;
|
||||
for (int i = 0; i < num_files; i++) {
|
||||
// Remove files that exceed the size limit but are older than one day.
|
||||
// This allows for temporary maintaining a larger cache size while
|
||||
// adjusting the configuration. The cache will be cleared the next day
|
||||
// for unused entries. We don't need to be overly aggressive with cache
|
||||
// cleaning; in most cases, it will not grow much, and in others, it may
|
||||
// actually be useful to cache more.
|
||||
cache_size += files[i].size;
|
||||
double rel_use = difftime(t, files[i].atime);
|
||||
if (cache_size > cache_limit && rel_use > 60 * 60 * 24) {
|
||||
MP_VERBOSE(p, "Removing %s | size: %9zu bytes | last used: %9d seconds ago\n",
|
||||
files[i].filepath, files[i].size, (int)rel_use);
|
||||
unlink(files[i].filepath);
|
||||
}
|
||||
int ret = pl_cache_save_file(cache->cache, file);
|
||||
fclose(file);
|
||||
if (ret >= 0)
|
||||
ret = rename(tmp, cache->path);
|
||||
if (ret < 0) {
|
||||
MP_WARN(p, "Failed saving cache to %s\n", cache->path);
|
||||
unlink(tmp);
|
||||
}
|
||||
|
||||
// fall through
|
||||
done:
|
||||
talloc_free(ta_ctx);
|
||||
pl_cache_destroy(&cache->cache);
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in New Issue