From aad7cf00bda1209980f2f611f2d60a9cf53db94d Mon Sep 17 00:00:00 2001 From: Martin Marinov <Martin.Marinov@autodesk.com> Date: Wed, 6 Apr 2022 21:06:29 +0100 Subject: [PATCH] MTBR-925 Add BufferedOutputFile (#79) Add buffered text output file for faster text writes. Improve platform compatibility for printing. --- Utils/BufferedOutputFile.hh | 102 ++++++++++++++++++++++++++++++++++++ Utils/CMakeLists.txt | 1 + Utils/OStringStream.cc | 24 +++++---- 3 files changed, 116 insertions(+), 11 deletions(-) create mode 100644 Utils/BufferedOutputFile.hh diff --git a/Utils/BufferedOutputFile.hh b/Utils/BufferedOutputFile.hh new file mode 100644 index 0000000..61222b1 --- /dev/null +++ b/Utils/BufferedOutputFile.hh @@ -0,0 +1,102 @@ +// (C) Copyright 2022 by Autodesk, Inc. + +#ifndef BASE_BUFFEREDOUTPUTFILE_HH_INCLUDED +#define BASE_BUFFEREDOUTPUTFILE_HH_INCLUDED + +#include <Base/Config/BaseDefines.hh> +#include <Base/Utils/IOutputStream.hh> +#include <Base/Debug/DebOut.hh> + +#include <cstdio> +#include <vector> + +namespace Base +{ + +// Provide buffered text print operations to a FILE, helps with the performance +// when storing large text data. +class BufferedOutputFile +{ +public: + BufferedOutputFile(const char* const _flnm, // filename + const size_t _flsh_size = 1 << 20, // buffer size that forces flush (1Mb) + const size_t _ovfl_size = 1 << 10 // "overflow" space in the buffer, also + // sets the maximum amount of characters that can be printed at once (1Kb) + ) + : flsh_size_(_flsh_size), ovfl_size_(_ovfl_size), + bffr_(flsh_size_ + ovfl_size_), bffr_end_(bffr_.data()) + { + const char* const MODE = "wb"; // open as write binary + +#ifdef _MSC_VER + fopen_s(&file_, _flnm, MODE); +#else + file_ = std::fopen(_flnm, MODE); +#endif + } + + ~BufferedOutputFile() + { + if (!opened()) + return; + flush(); + fclose(file_); + } + + bool opened() const { return file_ != nullptr; } + +#define DEB_check_opened \ + DEB_error_if(!opened(), "Write access to a file that has failed open") + + void flush() + { + DEB_check_opened; + const auto bffr_size = buffer_size(); + if (bffr_size == 0) + return; + fwrite(bffr_.data(), sizeof(char), bffr_size, file_); + bffr_end_ = bffr_.data(); + } + + template <typename... ArgT> void print(const ArgT&... _args) + { + DEB_check_opened; + const auto char_nmbr = Base::print(bffr_end_, ovfl_size_, _args...); + DEB_error_if(char_nmbr < 0 || char_nmbr >= static_cast<int>(ovfl_size_), + "print() result = " + << char_nmbr << " is unclear, consider using larger overflow size"); + bffr_end_ += char_nmbr; + if (buffer_size() + ovfl_size_ >= bffr_.size()) // make sure we always have + flush(); // at least ovfl_size_ space in the buffer + } + + template <typename T> void write(const T* const _ptr, const size_t _nmbr) + { + DEB_check_opened; + flush(); + fwrite(_ptr, sizeof(T), _nmbr, file_); + } + + // Access the file, but make sure we have flushed first + FILE* file() + { + DEB_check_opened; + flush(); + return file_; + } + +#undef DEB_check_opened + +private: + const size_t flsh_size_; // number of bytes to trigger flushing the buffer + const size_t ovfl_size_; // number of bytes to print in a single print() call + std::vector<char> bffr_; // buffer + char* bffr_end_; // end of the buffer + FILE* file_ = nullptr; + + size_t buffer_size() const { return bffr_end_ - bffr_.data(); } +}; + +} // namespace Base + +#endif // BASE_BUFFEREDOUTPUTFILE_HH_INCLUDED diff --git a/Utils/CMakeLists.txt b/Utils/CMakeLists.txt index 7fc0247..80ca9e9 100644 --- a/Utils/CMakeLists.txt +++ b/Utils/CMakeLists.txt @@ -1,6 +1,7 @@ set(my_headers ${CMAKE_CURRENT_SOURCE_DIR}/BaseError.hh ${CMAKE_CURRENT_SOURCE_DIR}/BaseErrorInc.hh + ${CMAKE_CURRENT_SOURCE_DIR}/BufferedOutputFile.hh ${CMAKE_CURRENT_SOURCE_DIR}/Environment.hh ${CMAKE_CURRENT_SOURCE_DIR}/FileOutput.hh ${CMAKE_CURRENT_SOURCE_DIR}/IOutputStream.hh diff --git a/Utils/OStringStream.cc b/Utils/OStringStream.cc index 5eba038..f1cf110 100644 --- a/Utils/OStringStream.cc +++ b/Utils/OStringStream.cc @@ -1,4 +1,4 @@ -// (C) Copyright 2019 by Autodesk, Inc. +// (C) Copyright 2022 by Autodesk, Inc. #include "Base/Security/Mandatory.hh" #include "OStringStream.hh" @@ -26,16 +26,22 @@ namespace Base namespace { +// Cross-platform approach to avoid buffer overflow when printing. However, +// there are subtle differences between these functions, see here for details: +// https://stackoverflow.com/questions/46485639/what-is-the-difference-between-vsnprintf-and-vsprintf-s + +#ifdef _MSC_VER + #define VSNPRINTF vsprintf_s +#else + #define VSNPRINTF vsnprintf +#endif + template <size_t _bffr_size> int sprintf_s(char (&_bffr)[_bffr_size], const char* _frmt, ...) { va_list arg_ptr; va_start(arg_ptr, _frmt); -#ifdef _MSC_VER - int res = vsprintf_s(_bffr, _bffr_size, _frmt, arg_ptr); -#else - int res = vsprintf(_bffr, _frmt, arg_ptr); -#endif // _MSC_VER + int res = VSNPRINTF(_bffr, _bffr_size, _frmt, arg_ptr); va_end(arg_ptr); return res; } @@ -46,11 +52,7 @@ int print(char* _bffr, const size_t _bffr_size, const char* _frmt, ...) { va_list arg_ptr; va_start(arg_ptr, _frmt); -#ifdef _MSC_VER - int res = vsprintf_s(_bffr, _bffr_size, _frmt, arg_ptr); -#else - int res = vsprintf(_bffr, _frmt, arg_ptr); -#endif + int res = VSNPRINTF(_bffr, _bffr_size, _frmt, arg_ptr); va_end(arg_ptr); return res; } -- GitLab