(no subject)
Jan. 20th, 2026 10:40 amГибридная Графовая База Данных (отвертка с моторчиком для моделирования моделей)
День 2. Вспомогательная функциональность, системы сборки и тестирования.
Хедер алиасов основных типов (с++). Чтобы не было разных типов в разных местах кода.
source egCoreTypes.h:
Система аллокации памяти для небольших кусочков данных "Ham Slicer". При использовании системной аллокации для таких данных получается значительный оверхед и повышенные риски утечек. Ham Slicer аллоцирует относительно большие блоки памяти и выделяет кусочки из них под поля данных.
source egHamSlicer.h:
Все данные в гибридной графовой базе хранятся в универсальном типе egByteArray. Контроль и интерпретация типов делается логикой приложения.
source egByteArray.h:
Чтобы хранить egByteArray с данными узла, создан класс EgPtrArrayType, который просто хранит несколько ссылок без контейнерного оверхеда.
Для унификации файловых и потоковых операций имплементированы соответствующие API - EgFileType и EgDataStream.
Система сборки основана на make как наиболее простой и обкатанной утилите. Сделаны отдельные конфигурации для сборки библиотеки и тестовых приложений плюс скрипты для сборки и запуска.
source Makefile.lib
Тестовые приложения запускают необходимую функциональность и пишут в лог строчку PASS или FAIL, скрипт потом их суммирует.
source all_tests.sh:
Резервное копирование производится двумя способами - git с внешним репозиторием GitHub плюс локальные tgz архивы через скрипт.
День 2. Вспомогательная функциональность, системы сборки и тестирования.
Хедер алиасов основных типов (с++). Чтобы не было разных типов в разных местах кода.
source egCoreTypes.h:
#pragma once #include <iostream> #include <cstdint> #include <map> #include <unordered_map> typedef unsigned char ByteType; // ID types typedef uint32_t EgDataNodeIDType; typedef EgDataNodeIDType EgDataLinkIDType; typedef uint16_t EgBlueprintIDType; // data nodes and links blueprint/layout/type/class ID type typedef uint16_t EgLayerNumType; // count/size/length types typedef uint16_t EgFieldNameLengthType; typedef uint8_t EgFieldsCountType; typedef uint16_t EgStrSizeType; typedef uint64_t StaticLengthType; // map types class EgDataNode; class EgDataLinkType; typedef std::unordered_map <EgDataNodeIDType, EgDataNode*> EgDataNodesMapType; typedef std::map <EgDataNodeIDType, EgDataNode*> EgDataNodesOrdMapType; typedef std::unordered_map <EgDataLinkIDType, EgDataNode*> EgLinkIDsNodePtrsMapType; typedef std::unordered_map <EgDataNode*, EgDataNode*> EgLinkDataPtrsNodePtrsMapType; const uint64_t egDefaultHamBrickSize = 1024;
Система аллокации памяти для небольших кусочков данных "Ham Slicer". При использовании системной аллокации для таких данных получается значительный оверхед и повышенные риски утечек. Ham Slicer аллоцирует относительно большие блоки памяти и выделяет кусочки из них под поля данных.
source egHamSlicer.h:
<#pragma once #include "../metainfo/egCoreTypes.h" typedef uint32_t EgHamBrickIDType; struct EgHamBrickType { EgHamBrickIDType brickID; ByteType* brickPtr; uint64_t freeSize; uint64_t usedSlicesCount; }; class EgHamSlicerType { public: EgHamBrickIDType nextID {1}; // ham bricks counter EgHamBrickType* newBrickPtr {nullptr}; EgHamBrickType newHamBrick; uint64_t hamBrickSize {egDefaultHamBrickSize}; // uint64_t dataSize {0}; std::unordered_map <EgHamBrickIDType, EgHamBrickType> hamBricks; // all ham bricks std::multimap <uint64_t, EgHamBrickType*> hamBricksByFree; // free slices of bricks lookup EgHamSlicerType () { initBrick(0); } ~EgHamSlicerType() { hamBricksByFree.clear(); hamBricks.clear(); } bool initBrick(uint64_t sliceSize) { // get new ham brick newBrickPtr = nullptr; if (sliceSize > hamBrickSize) return false; newHamBrick.brickPtr = new ByteType[hamBrickSize]; newHamBrick.brickID = nextID++; newHamBrick.freeSize = hamBrickSize - sliceSize; newHamBrick.usedSlicesCount = sliceSize > 0 ? 1 : 0; if (newHamBrick.brickPtr) { auto bricsIter = hamBricks.insert(std::make_pair(newHamBrick.brickID, newHamBrick)); // copy to map newBrickPtr = &(bricsIter.first-> second); hamBricksByFree.insert(std::make_pair(newHamBrick.freeSize, newBrickPtr)); } // std::cout << "EgHamSlicerType initBrick() exit" << std::endl; return (newHamBrick.brickPtr); } bool getSlice(uint64_t sliceSize, EgHamBrickIDType& brickID, ByteType*& slicePtr) { bool found {false}; // search by size for (auto [first, second] : hamBricksByFree) // 11 auto bricsIter :, <11 = dataFieldsNames.begin(); fieldsIter != dataFieldsNames.end(); ++fieldsIter) { if (first >= sliceSize) { // get existing ham brick brickID = second-> brickID; slicePtr = second-> brickPtr + hamBrickSize - second->freeSize; second-> freeSize -= sliceSize; second-> usedSlicesCount++; auto nodeHandler = hamBricksByFree.extract(first); // update key of map with magic 17 code nodeHandler.key() = second->freeSize; hamBricksByFree.insert(std::move(nodeHandler)); found = true; break; } if (!found) { // get new ham brick if (initBrick(sliceSize)) { brickID = newHamBrick.brickID; slicePtr = newHamBrick.brickPtr; } else return false; } // std::cout << "EgHamSlicerType getSlice() ok " << brickID << " " << sliceSize << std::endl; return true; } void freeSlice(EgHamBrickIDType brickID) { // check if ham brick totally consumed TODO recycle brick auto iter = hamBricks.find(brickID); // search hamBricks by ID if (iter != hamBricks.end()) iter-> second.usedSlicesCount--; if (!iter-> second.usedSlicesCount) { // all slices released, search hamBricksByFree by its freeSize range to remove for (auto [sizeIter, rangeEnd] = hamBricksByFree.equal_range(iter->second.freeSize); sizeIter != rangeEnd; ++sizeIter) if (sizeIter->second-> brickID == brickID) { hamBricksByFree.erase(sizeIter); break; } delete iter-> second.brickPtr; hamBricks.erase(iter); } } }; void PrintHamSlices(EgHamSlicerType theSlicer);
Все данные в гибридной графовой базе хранятся в универсальном типе egByteArray. Контроль и интерпретация типов делается логикой приложения.
source egByteArray.h:
#pragma once #include <cstring> #include "egHamSlicer.h" class EgByteArrayAbstractType { public: uint64_t dataSize {0}; // uint64_t dataChunkCapacity {0}; ByteType* dataChunk {nullptr}; EgByteArrayAbstractType () {} EgByteArrayAbstractType (uint64_t init_size) : dataSize(init_size) // , dataChunkCapacity(init_size) {} EgByteArrayAbstractType (ByteType* init_data, uint64_t init_size) : dataSize(init_size) // , dataChunkCapacity(init_size) , dataChunk(init_data) {} virtual ~EgByteArrayAbstractType() {} virtual void reallocDataChunk(uint64_t newSize) { std::cout << "ERROR: reallocDataChunk() of abstract class called" << std::endl; } EgByteArrayAbstractType& operator = (const EgByteArrayAbstractType& rightBA); template<typename T> EgByteArrayAbstractType& operator >> (T& value) { value = *(reinterpret_cast<T*> (this-> dataChunk)); return *this; } template<typename T> EgByteArrayAbstractType& operator << (const T& value) { reallocDataChunk(sizeof(T)); *(reinterpret_cast<T*> (dataChunk)) = value; return *this; } }; class EgByteArraySlicerType : public EgByteArrayAbstractType { // egBA with ham slicer mem allocator public: EgHamSlicerType* theHamSlicer {nullptr}; EgHamBrickIDType brickID {0}; EgByteArraySlicerType () = delete; EgByteArraySlicerType (EgHamSlicerType* a_HamSlicer, uint64_t init_size = 0): EgByteArrayAbstractType(init_size) , theHamSlicer(a_HamSlicer) { if(init_size) theHamSlicer-> getSlice(dataSize, brickID, dataChunk); } EgByteArraySlicerType (EgHamSlicerType& a_HamSlicer, uint64_t init_size = 0): EgByteArrayAbstractType(init_size) , theHamSlicer(&a_HamSlicer) { if(init_size) theHamSlicer-> getSlice(dataSize, brickID, dataChunk); } EgByteArraySlicerType (EgByteArraySlicerType& copySliceBA) // copy constructor { dataSize = copySliceBA.dataSize; if(dataSize) { theHamSlicer-> getSlice(dataSize, brickID, dataChunk); memcpy((void*)dataChunk, (void*) copySliceBA.dataChunk, dataSize);} } virtual ~EgByteArraySlicerType() { if (theHamSlicer && brickID) theHamSlicer-> freeSlice(brickID); } void reallocDataChunk(uint64_t newSize) override; }; class EgByteArraySysallocType : public EgByteArrayAbstractType { // egBA with system mem allocator public: EgByteArraySysallocType () {} EgByteArraySysallocType (uint64_t init_size): EgByteArrayAbstractType(init_size) { if(init_size) dataChunk = new ByteType[init_size]; } virtual ~EgByteArraySysallocType() { /*std::cout << "destr. of "; PrintByteArray(*this);*/ if(dataSize) delete dataChunk; } void reallocDataChunk(uint64_t newSize) override; }; void ByteArrayFromCharStr(const char* str, EgByteArrayAbstractType& byteArray); template <typename T> void ByteArrayFromType(T&& value, EgByteArrayAbstractType& byteArray) { // std::cout << "ByteArrayFromType() value: " << value << std::endl; byteArray.reallocDataChunk(sizeof(value)); memcpy((void*)byteArray.dataChunk, (void*) &value, byteArray.dataSize); } EgByteArrayAbstractType& operator >> (EgByteArrayAbstractType& byteArray, char* str); EgByteArrayAbstractType& operator << (EgByteArrayAbstractType& byteArray, const char* str); EgByteArrayAbstractType& operator >> (EgByteArrayAbstractType& byteArray, std::string& str); EgByteArrayAbstractType& operator << (EgByteArrayAbstractType& byteArray, const std::string& str); // ======================== Debug ======================== void PrintByteArray(EgByteArrayAbstractType& bArray, bool isStr = true);
Чтобы хранить egByteArray с данными узла, создан класс EgPtrArrayType, который просто хранит несколько ссылок без контейнерного оверхеда.
Для унификации файловых и потоковых операций имплементированы соответствующие API - EgFileType и EgDataStream.
Система сборки основана на make как наиболее простой и обкатанной утилите. Сделаны отдельные конфигурации для сборки библиотеки и тестовых приложений плюс скрипты для сборки и запуска.
source Makefile.lib
all: makethelib clean .PHONY: all clean # CFLAGS = -O3 -Wall -Wall -Wextra -pedantic -fPIC TARGET = libegdb.so CC = g++ LDLIBS += -lstdc++fs CPPFLAGS = -fPIC -std=c++20 INCLUDES = # -I/home/fran/some_directory # SRC_FILES = tests/testDataFields.cpp service/egByteArray.cpp nodes/egDataNode.cpp SRC_FILES_SERVICE = $(shell find service -name '*.cpp') SRC_FILES_NODES = $(shell find nodes -name '*.cpp') SRC_FILES_INDEXES = $(shell find indexes -name '*.cpp') SRC_FILES_LINKS = $(shell find links -name '*.cpp') SRC_FILES_METAINFO = $(shell find metainfo -name '*.cpp') # SRC_FILES_QTINTERFACE = $(shell find qtinterface -name '*.cpp') SRC_FILES = $(SRC_FILES_SERVICE) $(SRC_FILES_NODES) $(SRC_FILES_INDEXES) $(SRC_FILES_LINKS) $(SRC_FILES_METAINFO) # $(SRC_FILES_QTINTERFACE) # $(name:string1=string2) For each word in 'name' replace 'string1' with 'string2' OBJS = $(SRC_FILES:.cpp=.o) makethelib: $(TARGET) @echo =============== Finished compilation of $(TARGET) =============== @ls -la | grep $(TARGET) # compile object file to .so shared library $(INCLUDES) $(TARGET): $(OBJS) $(CC) -shared -o $(TARGET) $(OBJS) # compile source files to object files # this is a suffix replacement rule for building .o's from .c's # it uses automatic variables $<: the name of the prerequisite of # the rule(a .c file) and $@: the name of the target of the rule (a .o file) %.o : %.cpp $(CC) $(CPPFLAGS) $(INCLUDES) -c $< -o $@ clean: @echo =============== clean =============== $(RM) $(OBJS)
Тестовые приложения запускают необходимую функциональность и пишут в лог строчку PASS или FAIL, скрипт потом их суммирует.
source all_tests.sh:
#!/bin/bash ./clean.sh # rebuild echo ============ build lib and test apps ============ make -f Makefile.lib make -f Makefile.tests echo ============ run test apps ============ mv tests/*.exe tests_exec/ cd tests_exec # exec all *.exe to log file for i in *.exe do LD_LIBRARY_PATH=~/egdb/src ./$i 2>&1 | tee -a all_tests.log done echo # mv tests_exec/all_tests.log ./ PASSED_COUNT=`cat all_tests.log | grep PASS | wc -w` echo PASS total: $PASSED_COUNT FAILED_COUNT=`cat all_tests.log | grep FAIL | wc -w` echo FAIL total: $FAILED_COUNT cd ..
Резервное копирование производится двумя способами - git с внешним репозиторием GitHub плюс локальные tgz архивы через скрипт.