Kaynağa Gözat

LibAudio: Remove frame-wise copys from FlacLoader

Previously, FlacLoader would read the data for each frame into a
separate vector, which are then combined via extend() in the end. This
incurs an avoidable copy per frame. By having the next_frame() function
write into a given Span, there's only one vector allocated per call to
get_more_samples().

This increases performance by at least 100% realtime, as measured by
abench, from about 1200%-1300% to (usually) 1400% on complex test files.
kleines Filmröllchen 3 yıl önce
ebeveyn
işleme
b48badc3b6

+ 39 - 28
Userland/Libraries/LibAudio/FlacLoader.cpp

@@ -5,6 +5,7 @@
  */
 
 #include <AK/Debug.h>
+#include <AK/FixedArray.h>
 #include <AK/FlyString.h>
 #include <AK/Format.h>
 #include <AK/Math.h>
@@ -13,6 +14,7 @@
 #include <AK/String.h>
 #include <AK/StringBuilder.h>
 #include <AK/Try.h>
+#include <AK/TypedTransfer.h>
 #include <AK/UFixedBigInt.h>
 #include <LibAudio/Buffer.h>
 #include <LibAudio/FlacLoader.h>
@@ -190,41 +192,41 @@ MaybeLoaderError FlacLoaderPlugin::seek(const int position)
 
 LoaderSamples FlacLoaderPlugin::get_more_samples(size_t max_bytes_to_read_from_input)
 {
-    Vector<Sample> samples;
     ssize_t remaining_samples = static_cast<ssize_t>(m_total_samples - m_loaded_samples);
     if (remaining_samples <= 0)
         return Buffer::create_empty();
 
     size_t samples_to_read = min(max_bytes_to_read_from_input, remaining_samples);
-    samples.ensure_capacity(samples_to_read);
-    while (samples_to_read > 0) {
-        if (!m_current_frame.has_value())
-            TRY(next_frame());
-
-        // Do a full vector extend if possible
-        if (m_current_frame_data.size() <= samples_to_read) {
-            samples_to_read -= m_current_frame_data.size();
-            samples.extend(move(m_current_frame_data));
-            m_current_frame_data.clear();
-            m_current_frame.clear();
-        } else {
-            samples.unchecked_append(m_current_frame_data.data(), samples_to_read);
-            m_current_frame_data.remove(0, samples_to_read);
-            if (m_current_frame_data.size() == 0) {
-                m_current_frame.clear();
-            }
-            samples_to_read = 0;
-        }
+    auto samples = FixedArray<Sample>(samples_to_read);
+    size_t sample_index = 0;
+
+    if (m_unread_data.size() > 0) {
+        size_t to_transfer = min(m_unread_data.size(), samples_to_read);
+        dbgln_if(AFLACLOADER_DEBUG, "Reading {} samples from unread sample buffer (size {})", to_transfer, m_unread_data.size());
+        AK::TypedTransfer<Sample>::move(samples.data(), m_unread_data.data(), to_transfer);
+        if (to_transfer < m_unread_data.size())
+            m_unread_data.remove(0, to_transfer);
+        else
+            m_unread_data.clear_with_capacity();
+
+        sample_index += to_transfer;
+    }
+
+    while (sample_index < samples_to_read) {
+        TRY(next_frame(samples.span().slice(sample_index)));
+        sample_index += m_current_frame->sample_count;
+        if (m_stream->handle_any_error())
+            return LoaderError { LoaderError::Category::IO, m_loaded_samples, "Unknown I/O error" };
     }
 
-    m_loaded_samples += samples.size();
+    m_loaded_samples += sample_index;
     auto maybe_buffer = Buffer::create_with_samples(move(samples));
     if (maybe_buffer.is_error())
         return LoaderError { LoaderError::Category::Internal, m_loaded_samples, "Couldn't allocate sample buffer" };
     return maybe_buffer.release_value();
 }
 
-MaybeLoaderError FlacLoaderPlugin::next_frame()
+MaybeLoaderError FlacLoaderPlugin::next_frame(Span<Sample> target_vector)
 {
 #define FLAC_VERIFY(check, category, msg)                                                                                               \
     do {                                                                                                                                \
@@ -293,13 +295,14 @@ MaybeLoaderError FlacLoaderPlugin::next_frame()
     for (u8 i = 0; i < subframe_count; ++i) {
         FlacSubframeHeader new_subframe = TRY(next_subframe_header(bit_stream, i));
         Vector<i32> subframe_samples = TRY(parse_subframe(new_subframe, bit_stream));
-        current_subframes.append(move(subframe_samples));
+        current_subframes.unchecked_append(move(subframe_samples));
     }
 
     bit_stream.align_to_byte_boundary();
 
     // TODO: check checksum, see above
     [[maybe_unused]] u16 footer_checksum = static_cast<u16>(bit_stream.read_bits_big_endian(16));
+    dbgln_if(AFLACLOADER_DEBUG, "Subframe footer checksum: {}", footer_checksum);
 
     Vector<i32> left;
     Vector<i32> right;
@@ -352,17 +355,25 @@ MaybeLoaderError FlacLoaderPlugin::next_frame()
         break;
     }
 
-    VERIFY(left.size() == right.size());
+    VERIFY(left.size() == right.size() && left.size() == m_current_frame->sample_count);
 
     double sample_rescale = static_cast<double>(1 << (pcm_bits_per_sample(m_current_frame->bit_depth) - 1));
     dbgln_if(AFLACLOADER_DEBUG, "Sample rescaled from {} bits: factor {:.1f}", pcm_bits_per_sample(m_current_frame->bit_depth), sample_rescale);
 
-    m_current_frame_data.clear_with_capacity();
-    m_current_frame_data.ensure_capacity(left.size());
     // zip together channels
-    for (size_t i = 0; i < left.size(); ++i) {
+    auto samples_to_directly_copy = min(target_vector.size(), m_current_frame->sample_count);
+    for (size_t i = 0; i < samples_to_directly_copy; ++i) {
+        Sample frame = { left[i] / sample_rescale, right[i] / sample_rescale };
+        target_vector[i] = frame;
+    }
+    // move superflous data into the class buffer instead
+    auto result = m_unread_data.try_grow_capacity(m_current_frame->sample_count - samples_to_directly_copy);
+    if (result.is_error())
+        return LoaderError { LoaderError::Category::Internal, static_cast<size_t>(samples_to_directly_copy + m_current_sample_or_frame), "Couldn't allocate sample buffer for superflous data" };
+
+    for (size_t i = samples_to_directly_copy; i < m_current_frame->sample_count; ++i) {
         Sample frame = { left[i] / sample_rescale, right[i] / sample_rescale };
-        m_current_frame_data.unchecked_append(frame);
+        m_unread_data.unchecked_append(frame);
     }
 
     return {};

+ 4 - 3
Userland/Libraries/LibAudio/FlacLoader.h

@@ -111,8 +111,8 @@ private:
     // Either returns the metadata block or sets error message.
     // Additionally, increments m_data_start_location past the read meta block.
     ErrorOr<FlacRawMetadataBlock, LoaderError> next_meta_block(InputBitStream& bit_input);
-    // Fetches and sets the next FLAC frame
-    MaybeLoaderError next_frame();
+    // Fetches and writes the next FLAC frame
+    MaybeLoaderError next_frame(Span<Sample>);
     // Helper of next_frame that fetches a sub frame's header
     ErrorOr<FlacSubframeHeader, LoaderError> next_subframe_header(InputBitStream& bit_input, u8 channel_index);
     // Helper of next_frame that decompresses a subframe
@@ -151,7 +151,8 @@ private:
     u64 m_data_start_location { 0 };
     OwnPtr<FlacInputStream<FLAC_BUFFER_SIZE>> m_stream;
     Optional<FlacFrameHeader> m_current_frame;
-    Vector<Sample> m_current_frame_data;
+    // Whatever the last get_more_samples() call couldn't return gets stored here.
+    Vector<Sample, FLAC_BUFFER_SIZE> m_unread_data;
     u64 m_current_sample_or_frame { 0 };
 };