From f2100615b957c455d3023704714dc38dc178b392 Mon Sep 17 00:00:00 2001 From: "Steinar H. Gunderson" Date: Sat, 24 Nov 2018 22:48:44 +0100 Subject: [PATCH] Cache file descriptors when reading frames, for better readahead and fewer syscalls. --- frame_on_disk.cpp | 53 +++++++++++++++++++++++++++++++++++++++++++++ frame_on_disk.h | 15 ++++++++++++- jpeg_frame_view.cpp | 12 +++++----- jpeg_frame_view.h | 6 ++++- main.cpp | 30 ------------------------- meson.build | 2 +- video_stream.cpp | 12 +++++----- video_stream.h | 2 ++ 8 files changed, 87 insertions(+), 45 deletions(-) create mode 100644 frame_on_disk.cpp diff --git a/frame_on_disk.cpp b/frame_on_disk.cpp new file mode 100644 index 0000000..b496b3d --- /dev/null +++ b/frame_on_disk.cpp @@ -0,0 +1,53 @@ +#include +#include + +#include "frame_on_disk.h" + +using namespace std; + +FrameReader::~FrameReader() +{ + if (fd != -1) { + close(fd); + } +} + +string FrameReader::read_frame(FrameOnDisk frame) +{ + if (int(frame.filename_idx) != last_filename_idx) { + if (fd != -1) { + close(fd); // Ignore errors. + } + + string filename; + { + lock_guard lock(frame_mu); + filename = frame_filenames[frame.filename_idx]; + } + + fd = open(filename.c_str(), O_RDONLY); + if (fd == -1) { + perror(filename.c_str()); + exit(1); + } + + // We want readahead. (Ignore errors.) + posix_fadvise(fd, 0, 0, POSIX_FADV_SEQUENTIAL); + + last_filename_idx = frame.filename_idx; + } + + string str; + str.resize(frame.size); + off_t offset = 0; + while (offset < frame.size) { + int ret = pread(fd, &str[offset], frame.size - offset, frame.offset + offset); + if (ret <= 0) { + perror("pread"); + exit(1); + } + + offset += ret; + } + return str; +} diff --git a/frame_on_disk.h b/frame_on_disk.h index a0f46ad..1843857 100644 --- a/frame_on_disk.h +++ b/frame_on_disk.h @@ -19,6 +19,19 @@ struct FrameOnDisk { extern std::vector frames[MAX_STREAMS]; // Under frame_mu. extern std::vector frame_filenames; // Under frame_mu. -std::string read_frame(FrameOnDisk frame); +// A helper class to read frames from disk. It caches the file descriptor +// so that the kernel has a better chance of doing readahead when it sees +// the sequential reads. (For this reason, each display has a private +// FrameReader. Thus, we can easily keep multiple open file descriptors around +// for a single .frames file.) +class FrameReader { +public: + ~FrameReader(); + std::string read_frame(FrameOnDisk frame); + +private: + int fd = -1; + int last_filename_idx = -1; +}; #endif // !defined(_FRAME_ON_DISK_H) diff --git a/jpeg_frame_view.cpp b/jpeg_frame_view.cpp index be53688..3d3383f 100644 --- a/jpeg_frame_view.cpp +++ b/jpeg_frame_view.cpp @@ -207,7 +207,7 @@ void prune_cache() } } -shared_ptr decode_jpeg_with_cache(FrameOnDisk frame_spec, CacheMissBehavior cache_miss_behavior, bool *did_decode) +shared_ptr decode_jpeg_with_cache(FrameOnDisk frame_spec, CacheMissBehavior cache_miss_behavior, FrameReader *frame_reader, bool *did_decode) { *did_decode = false; { @@ -224,7 +224,7 @@ shared_ptr decode_jpeg_with_cache(FrameOnDisk frame_spec, CacheMissBehavi } *did_decode = true; - shared_ptr frame = decode_jpeg(read_frame(frame_spec)); + shared_ptr frame = decode_jpeg(frame_reader->read_frame(frame_spec)); unique_lock lock(cache_mu); cache_bytes_used += frame_size(*frame); @@ -236,7 +236,7 @@ shared_ptr decode_jpeg_with_cache(FrameOnDisk frame_spec, CacheMissBehavi return frame; } -void jpeg_decoder_thread_func() +void JPEGFrameView::jpeg_decoder_thread_func() { size_t num_decoded = 0, num_dropped = 0; @@ -281,7 +281,7 @@ void jpeg_decoder_thread_func() } bool found_in_cache; - shared_ptr frame = decode_jpeg_with_cache(frame_spec, cache_miss_behavior, &found_in_cache); + shared_ptr frame = decode_jpeg_with_cache(frame_spec, cache_miss_behavior, &decode.destination->frame_reader, &found_in_cache); if (frame == nullptr) { assert(cache_miss_behavior == RETURN_NULLPTR_IF_NOT_IN_CACHE); @@ -297,9 +297,9 @@ void jpeg_decoder_thread_func() } } if (subframe_idx == 0) { - primary_frame = move(frame); + primary_frame = std::move(frame); } else { - secondary_frame = move(frame); + secondary_frame = std::move(frame); } } if (drop) { diff --git a/jpeg_frame_view.h b/jpeg_frame_view.h index bc36283..38ffd41 100644 --- a/jpeg_frame_view.h +++ b/jpeg_frame_view.h @@ -21,7 +21,7 @@ enum CacheMissBehavior { }; std::shared_ptr decode_jpeg(const std::string &filename); -std::shared_ptr decode_jpeg_with_cache(FrameOnDisk id, CacheMissBehavior cache_miss_behavior, bool *did_decode); +std::shared_ptr decode_jpeg_with_cache(FrameOnDisk id, CacheMissBehavior cache_miss_behavior, FrameReader *frame_reader, bool *did_decode); class JPEGFrameView : public QGLWidget { Q_OBJECT @@ -50,6 +50,10 @@ protected: void paintGL() override; private: + static void jpeg_decoder_thread_func(); + + FrameReader frame_reader; + // The stream index of the latest frame we displayed. unsigned current_stream_idx = 0; diff --git a/main.cpp b/main.cpp index e04af22..681724a 100644 --- a/main.cpp +++ b/main.cpp @@ -421,33 +421,3 @@ int record_thread_func() return 0; } - -string read_frame(FrameOnDisk frame) -{ - string filename; - { - lock_guard lock(frame_mu); - filename = frame_filenames[frame.filename_idx]; - } - - // TODO: cache the open file handles - FILE *fp = fopen(filename.c_str(), "rb"); - if (fp == nullptr) { - perror(filename.c_str()); - exit(1); - } - if (fseek(fp, frame.offset, SEEK_SET) == -1) { - perror("fseek"); - exit(1); - } - - string str; - str.resize(frame.size); - if (fread(&str[0], frame.size, 1, fp) != 1) { - perror("fread"); - exit(1); - } - - fclose(fp); - return str; -} diff --git a/meson.build b/meson.build index 874cb78..410d713 100644 --- a/meson.build +++ b/meson.build @@ -44,7 +44,7 @@ srcs = ['flow.cpp', 'gpu_timers.cpp'] # All the other files. srcs += ['ffmpeg_raii.cpp', 'main.cpp', 'player.cpp', 'httpd.cpp', 'mux.cpp', 'metacube2.cpp', 'video_stream.cpp', 'context.cpp', 'chroma_subsampler.cpp'] srcs += ['vaapi_jpeg_decoder.cpp', 'memcpy_interleaved.cpp', 'db.cpp', 'disk_space_estimator.cpp', 'ycbcr_converter.cpp', 'flags.cpp'] -srcs += ['mainwindow.cpp', 'jpeg_frame_view.cpp', 'clip_list.cpp'] +srcs += ['mainwindow.cpp', 'jpeg_frame_view.cpp', 'clip_list.cpp', 'frame_on_disk.cpp'] srcs += moc_files srcs += proto_generated diff --git a/video_stream.cpp b/video_stream.cpp index a9ed061..d425ed1 100644 --- a/video_stream.cpp +++ b/video_stream.cpp @@ -294,7 +294,7 @@ void VideoStream::schedule_original_frame(steady_clock::time_point local_pts, // Preload the file from disk, so that the encoder thread does not get stalled. // TODO: Consider sending it through the queue instead. - (void)read_frame(frame); + (void)frame_reader.read_frame(frame); QueuedFrame qf; qf.local_pts = local_pts; @@ -334,8 +334,8 @@ void VideoStream::schedule_faded_frame(steady_clock::time_point local_pts, int64 bool did_decode; - shared_ptr frame1 = decode_jpeg_with_cache(frame1_spec, DECODE_IF_NOT_IN_CACHE, &did_decode); - shared_ptr frame2 = decode_jpeg_with_cache(frame2_spec, DECODE_IF_NOT_IN_CACHE, &did_decode); + shared_ptr frame1 = decode_jpeg_with_cache(frame1_spec, DECODE_IF_NOT_IN_CACHE, &frame_reader, &did_decode); + shared_ptr frame2 = decode_jpeg_with_cache(frame2_spec, DECODE_IF_NOT_IN_CACHE, &frame_reader, &did_decode); ycbcr_semiplanar_converter->prepare_chain_for_fade(frame1, frame2, fade_alpha)->render_to_fbo(resources->fade_fbo, 1280, 720); @@ -414,7 +414,7 @@ void VideoStream::schedule_interpolated_frame(steady_clock::time_point local_pts for (size_t frame_no = 0; frame_no < 2; ++frame_no) { FrameOnDisk frame_spec = frame_no == 1 ? frame2 : frame1; bool did_decode; - shared_ptr frame = decode_jpeg_with_cache(frame_spec, DECODE_IF_NOT_IN_CACHE, &did_decode); + shared_ptr frame = decode_jpeg_with_cache(frame_spec, DECODE_IF_NOT_IN_CACHE, &frame_reader, &did_decode); ycbcr_converter->prepare_chain_for_conversion(frame)->render_to_fbo(resources->input_fbos[frame_no], 1280, 720); } @@ -434,7 +434,7 @@ void VideoStream::schedule_interpolated_frame(steady_clock::time_point local_pts // Now decode the image we are fading against. bool did_decode; - shared_ptr frame2 = decode_jpeg_with_cache(secondary_frame, DECODE_IF_NOT_IN_CACHE, &did_decode); + shared_ptr frame2 = decode_jpeg_with_cache(secondary_frame, DECODE_IF_NOT_IN_CACHE, &frame_reader, &did_decode); // Then fade against it, putting it into the fade Y' and CbCr textures. ycbcr_semiplanar_converter->prepare_chain_for_fade_from_texture(qf.output_tex, frame2, fade_alpha)->render_to_fbo(resources->fade_fbo, 1280, 720); @@ -566,7 +566,7 @@ void VideoStream::encode_thread_func() if (qf.type == QueuedFrame::ORIGINAL) { // Send the JPEG frame on, unchanged. - string jpeg = read_frame(qf.frame1); + string jpeg = frame_reader.read_frame(qf.frame1); AVPacket pkt; av_init_packet(&pkt); pkt.stream_index = 0; diff --git a/video_stream.h b/video_stream.h index 6cc9471..736a20f 100644 --- a/video_stream.h +++ b/video_stream.h @@ -62,6 +62,8 @@ public: QueueSpotHolder &&queue_spot_holder); private: + FrameReader frame_reader; + void encode_thread_func(); std::thread encode_thread; -- 2.39.2