VolumeVerifier: Handle overlapping blocks more efficiently

The performance gains of doing this aren't too important since you
normally wouldn't run into any disc image that has overlapping blocks
(which by extension means overlapping partitions), but this change also
lets us get rid of things like VolumeVerifier's mutex that used to
exist just for the sake of handling overlapping blocks.
This commit is contained in:
JosJuice 2019-08-06 21:14:33 +02:00
parent 15ebb1d9e4
commit 10e1acf25c
2 changed files with 64 additions and 63 deletions

View File

@ -9,7 +9,6 @@
#include <future> #include <future>
#include <limits> #include <limits>
#include <memory> #include <memory>
#include <mutex>
#include <optional> #include <optional>
#include <string> #include <string>
#include <string_view> #include <string_view>
@ -1057,11 +1056,20 @@ void VolumeVerifier::WaitForAsyncOperations() const
bool VolumeVerifier::ReadChunkAndWaitForAsyncOperations(u64 bytes_to_read) bool VolumeVerifier::ReadChunkAndWaitForAsyncOperations(u64 bytes_to_read)
{ {
std::vector<u8> data(bytes_to_read); std::vector<u8> data(bytes_to_read);
const u64 bytes_to_copy = std::min(m_excess_bytes, bytes_to_read);
if (bytes_to_copy > 0)
std::memcpy(data.data(), m_data.data() + m_data.size() - m_excess_bytes, bytes_to_copy);
bytes_to_read -= bytes_to_copy;
if (bytes_to_read > 0)
{
if (!m_volume.Read(m_progress + bytes_to_copy, bytes_to_read, data.data() + bytes_to_copy,
PARTITION_NONE))
{ {
std::lock_guard lk(m_volume_mutex);
if (!m_volume.Read(m_progress, bytes_to_read, data.data(), PARTITION_NONE))
return false; return false;
} }
}
WaitForAsyncOperations(); WaitForAsyncOperations();
m_data = std::move(data); m_data = std::move(data);
@ -1080,6 +1088,7 @@ void VolumeVerifier::Process()
bool content_read = false; bool content_read = false;
bool block_read = false; bool block_read = false;
u64 bytes_to_read = BLOCK_SIZE; u64 bytes_to_read = BLOCK_SIZE;
u64 excess_bytes = 0;
if (m_content_index < m_content_offsets.size() && if (m_content_index < m_content_offsets.size() &&
m_content_offsets[m_content_index] == m_progress) m_content_offsets[m_content_index] == m_progress)
{ {
@ -1096,12 +1105,27 @@ void VolumeVerifier::Process()
{ {
bytes_to_read = VolumeWii::BLOCK_TOTAL_SIZE; bytes_to_read = VolumeWii::BLOCK_TOTAL_SIZE;
block_read = true; block_read = true;
if (m_block_index + 1 < m_blocks.size() &&
m_blocks[m_block_index + 1].offset < m_progress + bytes_to_read)
{
excess_bytes = m_progress + bytes_to_read - m_blocks[m_block_index + 1].offset;
}
} }
else if (m_block_index < m_blocks.size() && m_blocks[m_block_index].offset > m_progress) else if (m_block_index < m_blocks.size() && m_blocks[m_block_index].offset > m_progress)
{ {
bytes_to_read = std::min(bytes_to_read, m_blocks[m_block_index].offset - m_progress); bytes_to_read = std::min(bytes_to_read, m_blocks[m_block_index].offset - m_progress);
} }
bytes_to_read = std::min(bytes_to_read, m_max_progress - m_progress);
if (m_progress + bytes_to_read > m_max_progress)
{
const u64 bytes_over_max = m_progress + bytes_to_read - m_max_progress;
bytes_to_read -= bytes_over_max;
if (excess_bytes < bytes_over_max)
excess_bytes = 0;
else
excess_bytes -= bytes_over_max;
}
const bool is_data_needed = m_calculating_any_hash || content_read || block_read; const bool is_data_needed = m_calculating_any_hash || content_read || block_read;
const bool read_succeeded = is_data_needed && ReadChunkAndWaitForAsyncOperations(bytes_to_read); const bool read_succeeded = is_data_needed && ReadChunkAndWaitForAsyncOperations(bytes_to_read);
@ -1114,28 +1138,31 @@ void VolumeVerifier::Process()
m_calculating_any_hash = false; m_calculating_any_hash = false;
} }
m_excess_bytes = excess_bytes;
const u64 byte_increment = bytes_to_read - excess_bytes;
if (m_calculating_any_hash) if (m_calculating_any_hash)
{ {
if (m_hashes_to_calculate.crc32) if (m_hashes_to_calculate.crc32)
{ {
m_crc32_future = std::async(std::launch::async, [this] { m_crc32_future = std::async(std::launch::async, [this, byte_increment] {
// It would be nice to use crc32_z here instead of crc32, but it isn't available on Android // It would be nice to use crc32_z here instead of crc32, but it isn't available on Android
m_crc32_context = m_crc32_context =
crc32(m_crc32_context, m_data.data(), static_cast<unsigned int>(m_data.size())); crc32(m_crc32_context, m_data.data(), static_cast<unsigned int>(byte_increment));
}); });
} }
if (m_hashes_to_calculate.md5) if (m_hashes_to_calculate.md5)
{ {
m_md5_future = std::async(std::launch::async, [this] { m_md5_future = std::async(std::launch::async, [this, byte_increment] {
mbedtls_md5_update_ret(&m_md5_context, m_data.data(), m_data.size()); mbedtls_md5_update_ret(&m_md5_context, m_data.data(), byte_increment);
}); });
} }
if (m_hashes_to_calculate.sha1) if (m_hashes_to_calculate.sha1)
{ {
m_sha1_future = std::async(std::launch::async, [this] { m_sha1_future = std::async(std::launch::async, [this, byte_increment] {
mbedtls_sha1_update_ret(&m_sha1_context, m_data.data(), m_data.size()); mbedtls_sha1_update_ret(&m_sha1_context, m_data.data(), byte_increment);
}); });
} }
} }
@ -1152,61 +1179,36 @@ void VolumeVerifier::Process()
m_content_index++; m_content_index++;
} }
if (m_block_index < m_blocks.size() && if (block_read)
m_blocks[m_block_index].offset < m_progress + bytes_to_read)
{ {
m_block_future = std::async( m_block_future = std::async(std::launch::async, [this, read_succeeded,
std::launch::async, block_index = m_block_index] {
[this, read_succeeded, bytes_to_read](size_t block_index, u64 progress) { const BlockToVerify& block = m_blocks[block_index];
while (block_index < m_blocks.size() && if (read_succeeded &&
m_blocks[block_index].offset < progress + bytes_to_read) m_volume.CheckBlockIntegrity(block.block_index, m_data, block.partition))
{
bool success;
if (m_blocks[block_index].offset == progress)
{
success = read_succeeded &&
m_volume.CheckBlockIntegrity(m_blocks[block_index].block_index, m_data,
m_blocks[block_index].partition);
}
else
{
std::lock_guard lk(m_volume_mutex);
success = m_volume.CheckBlockIntegrity(m_blocks[block_index].block_index,
m_blocks[block_index].partition);
}
const u64 offset = m_blocks[block_index].offset;
if (success)
{ {
m_biggest_verified_offset = m_biggest_verified_offset =
std::max(m_biggest_verified_offset, offset + VolumeWii::BLOCK_TOTAL_SIZE); std::max(m_biggest_verified_offset, block.offset + VolumeWii::BLOCK_TOTAL_SIZE);
} }
else else
{ {
if (m_scrubber.CanBlockBeScrubbed(offset)) if (m_scrubber.CanBlockBeScrubbed(block.offset))
{ {
WARN_LOG_FMT(DISCIO, "Integrity check failed for unused block at {:#x}", offset); WARN_LOG_FMT(DISCIO, "Integrity check failed for unused block at {:#x}", block.offset);
m_unused_block_errors[m_blocks[block_index].partition]++; m_unused_block_errors[block.partition]++;
} }
else else
{ {
WARN_LOG_FMT(DISCIO, "Integrity check failed for block at {:#x}", offset); WARN_LOG_FMT(DISCIO, "Integrity check failed for block at {:#x}", block.offset);
m_block_errors[m_blocks[block_index].partition]++; m_block_errors[block.partition]++;
} }
} }
block_index++; });
}
},
m_block_index, m_progress);
while (m_block_index < m_blocks.size() &&
m_blocks[m_block_index].offset < m_progress + bytes_to_read)
{
m_block_index++; m_block_index++;
} }
}
m_progress += bytes_to_read; m_progress += byte_increment;
} }
u64 VolumeVerifier::GetBytesProcessed() const u64 VolumeVerifier::GetBytesProcessed() const

View File

@ -6,7 +6,6 @@
#include <future> #include <future>
#include <map> #include <map>
#include <mutex>
#include <optional> #include <optional>
#include <string> #include <string>
#include <vector> #include <vector>
@ -177,8 +176,8 @@ private:
mbedtls_md5_context m_md5_context; mbedtls_md5_context m_md5_context;
mbedtls_sha1_context m_sha1_context; mbedtls_sha1_context m_sha1_context;
u64 m_excess_bytes = 0;
std::vector<u8> m_data; std::vector<u8> m_data;
std::mutex m_volume_mutex;
std::future<void> m_crc32_future; std::future<void> m_crc32_future;
std::future<void> m_md5_future; std::future<void> m_md5_future;
std::future<void> m_sha1_future; std::future<void> m_sha1_future;