diff options
Diffstat (limited to 'FreeFileSync/Source/lib/binary.cpp')
-rwxr-xr-x[-rw-r--r--] | FreeFileSync/Source/lib/binary.cpp | 296 |
1 files changed, 141 insertions, 155 deletions
diff --git a/FreeFileSync/Source/lib/binary.cpp b/FreeFileSync/Source/lib/binary.cpp index 0d9b9549..5519bd37 100644..100755 --- a/FreeFileSync/Source/lib/binary.cpp +++ b/FreeFileSync/Source/lib/binary.cpp @@ -1,155 +1,141 @@ -// ***************************************************************************** -// * This file is part of the FreeFileSync project. It is distributed under * -// * GNU General Public License: http://www.gnu.org/licenses/gpl-3.0 * -// * Copyright (C) Zenju (zenju AT freefilesync DOT org) - All Rights Reserved * -// ***************************************************************************** - -#include "binary.h" -#include <vector> -#include <chrono> -//#include <zen/tick_count.h> - -using namespace zen; -using AFS = AbstractFileSystem; - -namespace -{ -/* -1. there seems to be no perf improvement possible when using file mappings instad of ::ReadFile() calls on Windows: - => buffered access: same perf - => unbuffered access: same perf on USB stick, file mapping 30% slower on local disk - -2. Tests on Win7 x64 show that buffer size does NOT matter if files are located on different physical disks! -Impact of buffer size when files are on same disk: - -buffer MB/s ------------- -64 10 -128 19 -512 40 -1024 48 -2048 56 -4096 56 -8192 56 -*/ - -const size_t BLOCK_SIZE_MAX = 16 * 1024 * 1024; - - -struct StreamReader -{ - StreamReader(const AbstractPath& filePath, const std::function<void(std::int64_t bytesDelta)>& notifyProgress, size_t& unevenBytes) : - stream_(AFS::getInputStream(filePath)), //throw FileError, (ErrorFileLocked) - defaultBlockSize_(stream_->getBlockSize()), - dynamicBlockSize_(defaultBlockSize_), - notifyProgress_(notifyProgress), - unevenBytes_(unevenBytes) {} - - void appendChunk(std::vector<char>& buffer) //throw FileError - { - assert(!eof_); - if (eof_) return; - - const auto startTime = std::chrono::steady_clock::now(); - - buffer.resize(buffer.size() + dynamicBlockSize_); - const size_t bytesRead = stream_->tryRead(&*(buffer.end() - dynamicBlockSize_), dynamicBlockSize_); //throw FileError; may return short, only 0 means EOF! => CONTRACT: bytesToRead > 0 - buffer.resize(buffer.size() - dynamicBlockSize_ + bytesRead); //caveat: unsigned arithmetics - - const auto stopTime = std::chrono::steady_clock::now(); - - //report bytes processed - if (notifyProgress_) - { - const size_t bytesToReport = (unevenBytes_ + bytesRead) / 2; - notifyProgress_(bytesToReport); //throw X! - unevenBytes_ = (unevenBytes_ + bytesRead) - bytesToReport * 2; //unsigned arithmetics! - } - - if (bytesRead == 0) - { - eof_ = true; - return; - } - - size_t proposedBlockSize = 0; - const auto loopTimeMs = std::chrono::duration_cast<std::chrono::milliseconds>(stopTime - startTime).count(); - - if (loopTimeMs >= 100) - lastDelayViolation_ = stopTime; - - //avoid "flipping back": e.g. DVD-ROMs read 32MB at once, so first read may be > 500 ms, but second one will be 0ms! - if (stopTime >= lastDelayViolation_ + std::chrono::seconds(2)) - { - lastDelayViolation_ = stopTime; - proposedBlockSize = dynamicBlockSize_ * 2; - } - if (loopTimeMs > 500) - proposedBlockSize = dynamicBlockSize_ / 2; - - if (defaultBlockSize_ <= proposedBlockSize && proposedBlockSize <= BLOCK_SIZE_MAX) - dynamicBlockSize_ = proposedBlockSize; - } - - bool isEof() const { return eof_; } - -private: - const std::unique_ptr<AFS::InputStream> stream_; - const size_t defaultBlockSize_; - size_t dynamicBlockSize_; - const std::function<void(std::int64_t bytesDelta)> notifyProgress_; - size_t& unevenBytes_; - std::chrono::steady_clock::time_point lastDelayViolation_ = std::chrono::steady_clock::now(); - bool eof_ = false; -}; -} - - -bool zen::filesHaveSameContent(const AbstractPath& filePath1, const AbstractPath& filePath2, const std::function<void(std::int64_t bytesDelta)>& notifyProgress) //throw FileError -{ - size_t unevenBytes = 0; - StreamReader reader1(filePath1, notifyProgress, unevenBytes); //throw FileError, (ErrorFileLocked) - StreamReader reader2(filePath2, notifyProgress, unevenBytes); // - - StreamReader* readerLow = &reader1; - StreamReader* readerHigh = &reader2; - - std::vector<char> bufferLow; - std::vector<char> bufferHigh; - - for (;;) - { - const size_t bytesChecked = bufferLow.size(); - - readerLow->appendChunk(bufferLow); //throw FileError - - if (bufferLow.size() > bufferHigh.size()) - { - bufferLow.swap(bufferHigh); - std::swap(readerLow, readerHigh); - } - - if (!std::equal(bufferLow. begin() + bytesChecked, bufferLow.end(), - bufferHigh.begin() + bytesChecked)) - return false; - - if (readerLow->isEof()) - { - if (bufferLow.size() < bufferHigh.size()) - return false; - if (readerHigh->isEof()) - break; - //bufferLow.swap(bufferHigh); not needed - std::swap(readerLow, readerHigh); - } - - //don't let sliding buffer grow too large - bufferHigh.erase(bufferHigh.begin(), bufferHigh.begin() + bufferLow.size()); - bufferLow.clear(); - } - - if (unevenBytes != 0) - throw std::logic_error("Contract violation! " + std::string(__FILE__) + ":" + numberTo<std::string>(__LINE__)); - - return true; -} +// *****************************************************************************
+// * This file is part of the FreeFileSync project. It is distributed under *
+// * GNU General Public License: http://www.gnu.org/licenses/gpl-3.0 *
+// * Copyright (C) Zenju (zenju AT freefilesync DOT org) - All Rights Reserved *
+// *****************************************************************************
+
+#include "binary.h"
+#include <vector>
+#include <chrono>
+
+using namespace zen;
+using AFS = AbstractFileSystem;
+
+namespace
+{
+/*
+1. there seems to be no perf improvement possible when using file mappings instad of ::ReadFile() calls on Windows:
+ => buffered access: same perf
+ => unbuffered access: same perf on USB stick, file mapping 30% slower on local disk
+
+2. Tests on Win7 x64 show that buffer size does NOT matter if files are located on different physical disks!
+
+Impact of buffer size when files are on same disk:
+
+buffer MB/s
+------------
+ 64 10
+ 128 19
+ 512 40
+1024 48
+2048 56
+4096 56
+8192 56
+*/
+const size_t BLOCK_SIZE_MAX = 16 * 1024 * 1024;
+
+
+struct StreamReader
+{
+ StreamReader(const AbstractPath& filePath, const IOCallback& notifyUnbufferedIO) :
+ stream_(AFS::getInputStream(filePath, notifyUnbufferedIO)), //throw FileError, (ErrorFileLocked)
+ defaultBlockSize_(stream_->getBlockSize()),
+ dynamicBlockSize_(defaultBlockSize_) { assert(defaultBlockSize_ > 0); }
+
+ void appendChunk(std::vector<char>& buffer) //throw FileError, X
+ {
+ assert(!eof_);
+ if (eof_) return;
+
+ buffer.resize(buffer.size() + dynamicBlockSize_);
+
+ const auto startTime = std::chrono::steady_clock::now();
+ const size_t bytesRead = stream_->read(&*(buffer.end() - dynamicBlockSize_), dynamicBlockSize_); //throw FileError, X; return "bytesToRead" bytes unless end of stream!
+ const auto stopTime = std::chrono::steady_clock::now();
+
+ buffer.resize(buffer.size() - dynamicBlockSize_ + bytesRead); //caveat: unsigned arithmetics
+
+ if (bytesRead < dynamicBlockSize_)
+ {
+ eof_ = true;
+ return;
+ }
+
+ size_t proposedBlockSize = 0;
+ const auto loopTimeMs = std::chrono::duration_cast<std::chrono::milliseconds>(stopTime - startTime).count();
+
+ if (loopTimeMs >= 100)
+ lastDelayViolation_ = stopTime;
+
+ //avoid "flipping back": e.g. DVD-ROMs read 32MB at once, so first read may be > 500 ms, but second one will be 0ms!
+ if (stopTime >= lastDelayViolation_ + std::chrono::seconds(2))
+ {
+ lastDelayViolation_ = stopTime;
+ proposedBlockSize = dynamicBlockSize_ * 2;
+ }
+ if (loopTimeMs > 500)
+ proposedBlockSize = dynamicBlockSize_ / 2;
+
+ if (defaultBlockSize_ <= proposedBlockSize && proposedBlockSize <= BLOCK_SIZE_MAX)
+ dynamicBlockSize_ = proposedBlockSize;
+ }
+
+ bool isEof() const { return eof_; }
+
+private:
+ const std::unique_ptr<AFS::InputStream> stream_;
+ const size_t defaultBlockSize_;
+ size_t dynamicBlockSize_;
+ std::chrono::steady_clock::time_point lastDelayViolation_ = std::chrono::steady_clock::now();
+ bool eof_ = false;
+};
+}
+
+
+bool zen::filesHaveSameContent(const AbstractPath& filePath1, const AbstractPath& filePath2, const IOCallback& notifyUnbufferedIO) //throw FileError
+{
+ int64_t totalUnbufferedIO = 0;
+
+ StreamReader reader1(filePath1, IOCallbackDivider(notifyUnbufferedIO, totalUnbufferedIO)); //throw FileError, (ErrorFileLocked)
+ StreamReader reader2(filePath2, IOCallbackDivider(notifyUnbufferedIO, totalUnbufferedIO)); //
+
+ StreamReader* readerLow = &reader1;
+ StreamReader* readerHigh = &reader2;
+
+ std::vector<char> bufferLow;
+ std::vector<char> bufferHigh;
+
+ for (;;)
+ {
+ readerLow->appendChunk(bufferLow); //throw FileError, X
+
+ if (bufferLow.size() > bufferHigh.size())
+ {
+ bufferLow.swap(bufferHigh);
+ std::swap(readerLow, readerHigh);
+ }
+
+ if (!std::equal(bufferLow. begin(), bufferLow.end(),
+ bufferHigh.begin()))
+ return false;
+
+ if (readerLow->isEof())
+ {
+ if (bufferLow.size() < bufferHigh.size())
+ return false;
+ if (readerHigh->isEof())
+ break;
+ //bufferLow.swap(bufferHigh); not needed
+ std::swap(readerLow, readerHigh);
+ }
+
+ //don't let sliding buffer grow too large
+ bufferHigh.erase(bufferHigh.begin(), bufferHigh.begin() + bufferLow.size());
+ bufferLow.clear();
+ }
+
+ if (totalUnbufferedIO % 2 != 0)
+ throw std::logic_error("Contract violation! " + std::string(__FILE__) + ":" + numberTo<std::string>(__LINE__));
+
+ return true;
+}
|