From 084e71b1bf1cba51e789a5c7f3c279bdd9608346 Mon Sep 17 00:00:00 2001 From: Pascal Eberlein Date: Fri, 3 Sep 2021 00:34:22 +0200 Subject: [PATCH] v1.1 --- CMakeLists.txt | 7 +++ README.md | 21 +++++++ benchmarks.cpp | 155 +++++++++++++++++++++++++++++++++++++++++++++++++ binfmt.h | 53 ++++++++++++++++- tests.cpp | 8 ++- 5 files changed, 240 insertions(+), 4 deletions(-) create mode 100644 benchmarks.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index 098e3c0..599b611 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -18,9 +18,16 @@ if(TESTS) set(gtest_force_shared_crt ON CACHE BOOL "" FORCE) FetchContent_MakeAvailable(googletest) enable_testing() + add_executable(binfmt_tests tests.cpp) + add_executable(binfmt_benchmarks benchmarks.cpp) + target_compile_definitions(binfmt_tests PRIVATE -DTESTS) + target_compile_definitions(binfmt_benchmarks PRIVATE -DTESTS) + target_link_libraries(binfmt_tests gtest_main) + target_link_libraries(binfmt_benchmarks gtest_main) + include(GoogleTest) gtest_discover_tests(binfmt_tests) endif() diff --git a/README.md b/README.md index 4bc765c..ec54fb1 100644 --- a/README.md +++ b/README.md @@ -2,6 +2,27 @@ [![CodeFactor](https://www.codefactor.io/repository/github/nbdy/binfmt/badge)](https://www.codefactor.io/repository/github/nbdy/binfmt)
`A header only framework for binary file formats` +## benchmarks +### single insert (append EntryType) +|Number|Time (ms)| +|------|---------| +|1k |1290 | +|10k |13260 | +|100k |toolong | + +### vector insert (append std::vector) +|Number|Time (ms)| +|------|---------| +|1k |< 0 s | +|1M |~ 2 s | + + +### read +|Number|Time (ms)| +|------|---------| +|1M |< 0 s | + + ## Minimal example ```c++ #include diff --git a/benchmarks.cpp b/benchmarks.cpp new file mode 100644 index 0000000..b9c511d --- /dev/null +++ b/benchmarks.cpp @@ -0,0 +1,155 @@ +// +// Created by nbdy on 02.09.21. +// + +#include +#include +#include "binfmt.h" + +#define TEST_DIRECTORY "/tmp/xxxxxxxxxx__xxxxxxxxxxx__xx" +#define TEST_BINARY_FILE "/tmp/xxxxxxx__xxxxxxxxx.bin" +#define TEST_MAX_ENTRIES 1000000000 + +#define TIMESTAMP std::chrono::high_resolution_clock::to_time_t(std::chrono::high_resolution_clock::now()) + +std::string TIMEIT_NAME = "TIMEIT"; +time_t TIMEIT_START_TIMESTAMP = 0; +time_t TIMEIT_END_TIMESTAMP = 0; +time_t TIMEIT_DIFF = 0; + +#define TIMEIT_START(name) \ +TIMEIT_NAME = name; \ +TIMEIT_START_TIMESTAMP = TIMESTAMP; + +#define TIMEIT_END \ +TIMEIT_END_TIMESTAMP = TIMESTAMP; + +#define TIMEIT_RESULT \ +TIMEIT_DIFF = TIMEIT_END_TIMESTAMP - TIMEIT_START_TIMESTAMP; \ +std::cout << TIMEIT_NAME << ": " << std::to_string(TIMEIT_DIFF) << " s" << std::endl; + +struct TestBinaryFileHeader : public BinaryFileHeaderBase { + TestBinaryFileHeader(): BinaryFileHeaderBase(0x7357, 0x8888) {} +}; + +struct TestBinaryEntry { + uint32_t uNumber; + int32_t iNumber; + float fNumber; + char cChar; +}; + +typedef BinaryEntryContainer TestBinaryEntryContainer; +typedef BinaryFile TestBinaryFile; + + +TestBinaryFile getRandomTestFile() { + TestBinaryFile r(TEST_BINARY_FILE, TestBinaryFileHeader{}); + EXPECT_TRUE(Fs::exists(r.getFilePath())); + return r; +} + +char generateRandomChar(){ + return 'A' + std::rand() % 24; // NOLINT(cert-msc50-cpp,cppcoreguidelines-narrowing-conversions) +} + +int generateRandomInteger() { + return std::rand() % 10000000; // NOLINT(cert-msc50-cpp) +} + +float generateRandomFloat() { + return static_cast(std::rand()) / static_cast(RAND_MAX); // NOLINT(cert-msc50-cpp) +} + +TestBinaryEntryContainer generateRandomTestEntryContainer() { + return TestBinaryEntryContainer(TestBinaryEntry { + static_cast(generateRandomInteger()), + generateRandomInteger(), + generateRandomFloat(), + generateRandomChar() + }); +} + +void cleanupTestFile(TestBinaryFile f) { + f.deleteFile(); + EXPECT_FALSE(Fs::exists(f.getFilePath())); +} + +TEST(BinaryFile, test1kInsert) { + auto t = getRandomTestFile(); + std::vector entries; + for(uint32_t i = 0; i < 1000; i++) { + entries.push_back(generateRandomTestEntryContainer().entry); + } + TIMEIT_START("1kWrite") + auto r = t.append(entries); + TIMEIT_END + TIMEIT_RESULT + EXPECT_TRUE(r.ok); + EXPECT_EQ(t.getFileSize(), 1000 * sizeof(TestBinaryEntryContainer) + sizeof(TestBinaryFileHeader)); + cleanupTestFile(t); +} + +TEST(BinaryFile, test10kInsert) { + auto t = getRandomTestFile(); + std::vector entries; + for(uint32_t i = 0; i < 10000; i++) { + entries.push_back(generateRandomTestEntryContainer().entry); + } + TIMEIT_START("10kWrite") + auto r = t.append(entries); + TIMEIT_END + TIMEIT_RESULT + EXPECT_TRUE(r.ok); + EXPECT_EQ(t.getFileSize(), 10000 * sizeof(TestBinaryEntryContainer) + sizeof(TestBinaryFileHeader)); + int ec = 0; + std::vector allEntries; + EXPECT_TRUE(t.getAllEntries(allEntries)); + for(const auto& entry : allEntries) { + EXPECT_EQ(entry.checksum, TestBinaryEntryContainer(entries[ec]).checksum); + ec++; + } + cleanupTestFile(t); +} + +TEST(BinaryFile, test100kInsert) { + auto t = getRandomTestFile(); + std::vector entries; + for(uint32_t i = 0; i < 100000; i++) { + entries.push_back(generateRandomTestEntryContainer().entry); + } + TIMEIT_START("100kWrite") + auto r = t.append(entries); + TIMEIT_END + TIMEIT_RESULT + EXPECT_TRUE(r.ok); + EXPECT_EQ(t.getFileSize(), 100000 * sizeof(TestBinaryEntryContainer) + sizeof(TestBinaryFileHeader)); + cleanupTestFile(t); +} + +TEST(BinaryFile, test1MInsert) { + auto t = getRandomTestFile(); + std::vector entries; + uint32_t insertCount = 10000000; + std::cout << "Generating file of size " << std::to_string(insertCount * sizeof(TestBinaryEntryContainer) + sizeof(TestBinaryFileHeader)) << std::endl; + for(uint32_t i = 0; i < insertCount; i++) { + entries.push_back(generateRandomTestEntryContainer().entry); + } + TIMEIT_START("1MWrite") + auto r = t.append(entries); + TIMEIT_END + TIMEIT_RESULT + EXPECT_TRUE(r.ok); + EXPECT_EQ(t.getFileSize(), insertCount * sizeof(TestBinaryEntryContainer) + sizeof(TestBinaryFileHeader)); + int ec = 0; + std::vector allEntries; + TIMEIT_START("1MRead") + EXPECT_TRUE(t.getAllEntries(allEntries)); + TIMEIT_END + TIMEIT_RESULT + for(const auto& entry : allEntries) { + EXPECT_EQ(entry.checksum, TestBinaryEntryContainer(entries[ec]).checksum); + ec++; + } + //cleanupTestFile(t); +} \ No newline at end of file diff --git a/binfmt.h b/binfmt.h index 5249a8e..68eb346 100644 --- a/binfmt.h +++ b/binfmt.h @@ -145,7 +145,13 @@ struct FileUtils { } if (Fs::exists(FilePath) || Create) { r = std::fopen(FilePath.c_str(), Create ? "wbe" : "r+be"); - if (fseek(r, offset, SEEK_SET) != 0) { + auto fn = fileno(r); + if(lockf(fn, F_LOCK, 0) != 0) { + (void) CloseBinaryFile(r); + r = nullptr; + } + auto sr = fseek(r, offset, SEEK_SET); + if (sr != 0) { (void) CloseBinaryFile(r); r = nullptr; } @@ -159,7 +165,9 @@ struct FileUtils { * @return */ static bool CloseBinaryFile(FILE* fp) { - auto r = syncfs(fileno(fp)); + auto fn = fileno(fp); + (void) lockf(fn, F_ULOCK, 0); + auto r = syncfs(fn); return fclose(fp) == 0 && r == 0; } @@ -231,6 +239,26 @@ struct FileUtils { return r == 1; } + /*! + * Write data entry to file. Skips size of HeaderType in bytes. + * @tparam EntryType + * @param FilePath + * @param entry + * @return false if file does not exist, seek to offset failed or we can't write the object + */ + template + static bool WriteDataVector(const std::string& FilePath, std::vector entries, uint32_t offset = 0) { + uint32_t o = sizeof(HeaderType) + offset * sizeof(EntryType); + FILE* fp = OpenBinaryFile(FilePath, false, o); + if (fp == nullptr) { + std::cout << strerror(errno) << std::endl; + return false; + } + auto r = fwrite(&entries[0], sizeof(EntryType), entries.size(), fp); + (void) CloseBinaryFile(fp); + return r == entries.size(); + } + /*! * * @tparam HeaderType @@ -482,6 +510,27 @@ class BinaryFile { return AppendResult{rewind, ok, m_uCurrentAppendOffset}; } + AppendResult append(std::vector entries) { + AppendResult r { + false, true, 0 + }; + bool rewind = false; + if(getFileSize() + sizeof(ContainerType) * entries.size() > m_uMaxFileSize) { + r.rewind = true; + auto sizeLeft = m_uMaxFileSize - getFileSize(); + uint32_t entriesLeft = sizeLeft / sizeof(ContainerType) - 1; + std::vector tmpEntries(entries.begin(), entries.begin() + entriesLeft); + entries = std::vector(entries.begin() + entriesLeft + 1, entries.end()); + r = append(tmpEntries); + } + std::vector containers; + for(const auto& entry : entries) { + containers.push_back(ContainerType(entry)); + } + r.ok = FileUtils::WriteDataVector(m_sFilePath, containers); + return r; + } + bool setEntryAt(EntryType entry, uint32_t position) { return FileUtils::WriteData(m_sFilePath, ContainerType(entry), position); } diff --git a/tests.cpp b/tests.cpp index 36ae319..c022f6b 100644 --- a/tests.cpp +++ b/tests.cpp @@ -3,11 +3,14 @@ // #include "gtest/gtest.h" +#include #include "binfmt.h" +#include #define TEST_DIRECTORY "/tmp/xxxxxxxxxx__xxxxxxxxxxx__xx" #define TEST_BINARY_FILE "/tmp/xxxxxxx__xxxxxxxxx.bin" -#define TEST_MAX_ENTRIES 50 +#define TEST_MAX_ENTRIES 1000 + struct TestBinaryFileHeader : public BinaryFileHeaderBase { TestBinaryFileHeader(): BinaryFileHeaderBase(0x7357, 0x8888) {} @@ -63,7 +66,7 @@ std::vector appendRandomAmountOfEntriesV(TestBinaryFil return r; } -std::vector appendExactAmountOfEntriesV(TestBinaryFile f, int count = 42) { +std::vector appendExactAmountOfEntriesV(TestBinaryFile f, uint32_t count = 42) { std::vector r; for(int i = 0; i < count; i++) { auto v = generateRandomTestEntryContainer(); @@ -286,3 +289,4 @@ TEST(BinaryFile, testRemoveEntryAt) { EXPECT_EQ(t0.checksum, t1.checksum); cleanupTestFile(t); } +