diff --git a/benchmarks/CMakeLists.txt b/benchmarks/CMakeLists.txt index a76ebf1e..b9c54288 100644 --- a/benchmarks/CMakeLists.txt +++ b/benchmarks/CMakeLists.txt @@ -39,6 +39,11 @@ add_executable(hash_map hash_map.cpp) target_include_directories(hash_map PUBLIC ../catkit_core) target_link_libraries(hash_map PUBLIC catkit_core) +# Uuid generator benchmark +add_executable(uuid_generator uuid_generator.cpp) +target_include_directories(uuid_generator PUBLIC ../catkit_core) +target_link_libraries(uuid_generator PUBLIC catkit_core) + # Add install files install(TARGETS datastream_latency DESTINATION bin) install(TARGETS datastream_submit DESTINATION bin) @@ -46,3 +51,4 @@ install(TARGETS timestamp DESTINATION bin) install(TARGETS free_list_allocator DESTINATION bin) install(TARGETS pool_allocator DESTINATION bin) install(TARGETS hash_map DESTINATION bin) +install(TARGETS uuid_generator DESTINATION bin) diff --git a/benchmarks/uuid_generator.cpp b/benchmarks/uuid_generator.cpp new file mode 100644 index 00000000..8db6341a --- /dev/null +++ b/benchmarks/uuid_generator.cpp @@ -0,0 +1,32 @@ +#include "UuidGenerator.h" +#include "Timing.h" + +#include + +int main() +{ + const size_t N = 100000000; + + UuidGenerator generator; + + char uuid[16]; + + std::cout << std::hex; + + auto start = GetTimeStamp(); + + for (size_t i = 0; i < N; ++i) + { + generator.Generate(uuid); + } + + auto end = GetTimeStamp(); + + std::cout << std::dec; + + std::cout << "Time: " << (end - start) / 1e9 << " sec" << std::endl; + std::cout << "Throughput: " << N / ((end - start) / 1e9) << " ops/s" << std::endl; + std::cout << "Time per operation: " << (end - start) / N << " ns" << std::endl; + + return 0; +} diff --git a/catkit_core/CMakeLists.txt b/catkit_core/CMakeLists.txt index 6258d432..2264c90d 100644 --- a/catkit_core/CMakeLists.txt +++ b/catkit_core/CMakeLists.txt @@ -35,6 +35,9 @@ add_library(catkit_core STATIC Util.cpp PoolAllocator.cpp FreeListAllocator.cpp + MessageBroker.cpp + UuidGenerator.cpp + LocalMemory.cpp proto/core.pb.cc proto/logging.pb.cc proto/testbed.pb.cc diff --git a/catkit_core/CudaSharedMemory.h b/catkit_core/CudaSharedMemory.h new file mode 100644 index 00000000..a5e433f5 --- /dev/null +++ b/catkit_core/CudaSharedMemory.h @@ -0,0 +1,30 @@ +#ifndef CUDA_SHARED_MEMORY_H +#define CUDA_SHARED_MEMORY_H + +#include "Memory.h" + +#include + +#ifdef HAVE_CUDA +#include +typedef cudaIpcMemHandle_t CudaIpcHandle; +#else +// CUDA cudaIpcMemHandle_t is a struct of 64 bytes. +typedef char CudaIpcHandle[64]; +#endif + +class CudaSharedMemory : public Memory +{ +private: + CudaSharedMemory(const CudaIpcHandle &ipc_handle, void *device_pointer=nullptr); + +public: + ~CudaSharedMemory(); + + static std::shared_ptr Create(size_t num_bytes_in_buffer); + static std::shared_ptr Open(const CudaIpcHandle &ipc_handle); + + void *GetAddress(std::size_t offset = 0) override; +}; + +#endif // CUDA_SHARED_MEMORY_H diff --git a/catkit_core/LocalMemory.cpp b/catkit_core/LocalMemory.cpp new file mode 100644 index 00000000..57120fc0 --- /dev/null +++ b/catkit_core/LocalMemory.cpp @@ -0,0 +1,16 @@ +#include "LocalMemory.h" + +LocalMemory::LocalMemory(std::size_t num_bytes) + : m_Memory(new char[num_bytes]) +{ +} + +LocalMemory::~LocalMemory() +{ + delete[] m_Memory; +} + +void *LocalMemory::GetAddress(std::size_t offset) +{ + return m_Memory + offset; +} diff --git a/catkit_core/LocalMemory.h b/catkit_core/LocalMemory.h new file mode 100644 index 00000000..d237b9db --- /dev/null +++ b/catkit_core/LocalMemory.h @@ -0,0 +1,18 @@ +#ifndef LOCAL_MEMORY_H +#define LOCAL_MEMORY_H + +#include "Memory.h" + +class LocalMemory : public Memory +{ +public: + LocalMemory(std::size_t num_bytes); + virtual ~LocalMemory(); + + virtual void *GetAddress(std::size_t offset = 0); + +private: + char *m_Memory; +}; + +#endif // LOCAL_MEMORY_H diff --git a/catkit_core/Memory.h b/catkit_core/Memory.h new file mode 100644 index 00000000..9f20e9ed --- /dev/null +++ b/catkit_core/Memory.h @@ -0,0 +1,16 @@ +#ifndef MEMORY_H +#define MEMORY_H + +#include + +class Memory +{ +public: + virtual ~Memory() + { + } + + virtual void *GetAddress(std::size_t offset = 0) = 0; +}; + +#endif // MEMORY_H diff --git a/catkit_core/MessageBroker.cpp b/catkit_core/MessageBroker.cpp new file mode 100644 index 00000000..89245a03 --- /dev/null +++ b/catkit_core/MessageBroker.cpp @@ -0,0 +1,201 @@ +#include "MessageBroker.h" + +#include "Util.h" +#include "Timing.h" +#include "HostName.h" + +#include + +TopicHeader::TopicHeader(const TopicHeader &header) +{ + CopyFrom(header); +} + +TopicHeader &TopicHeader::operator=(const TopicHeader &header) +{ + CopyFrom(header); + + return *this; +} + +void TopicHeader::CopyFrom(const TopicHeader &header) +{ + next_frame_id.store(header.next_frame_id.load(std::memory_order_relaxed), std::memory_order_relaxed); + synchronization = header.synchronization; + + std::copy(header.message_offsets, header.message_offsets + TOPIC_MAX_NUM_MESSAGES, message_offsets); + std::copy((char *)header.metadata_keys, (char *)header.metadata_keys + sizeof(metadata_keys), (char *)metadata_keys); +} + +Message MessageBroker::PrepareMessage(const std::string &topic, size_t payload_size, int8_t device_id) +{ + Uuid trace_id; + m_UuidGenerator.Generate(trace_id); + + return PrepareMessage(topic, trace_id, payload_size, device_id); +} + +Message MessageBroker::PrepareMessage(const std::string &topic, Uuid trace_id, size_t payload_size, int8_t device_id) +{ + Message message; + + message.m_HasBeenPublished = false; + message.m_MessageBroker = shared_from_this(); + + // Allocate a payload. + auto allocator = GetAllocator(device_id); + + if (allocator == nullptr) + { + throw std::runtime_error("Invalid device ID."); + } + + auto block_handle = allocator->Allocate(payload_size); + + if (block_handle == FreeListAllocator::INVALID_HANDLE) + { + throw std::runtime_error("Could not allocate payload."); + } + + auto offset = allocator->GetOffset(block_handle); + + if (device_id < 0) + { + message.m_Payload = m_CpuPayloadMemory->GetAddress(offset); + } + else + { + message.m_Payload = m_GpuPayloadMemory[device_id]->GetAddress(offset); + } + + // Allocate a message header. + auto message_header_handle = m_MessageHeaderAllocator.Allocate(); + + if (message_header_handle == PoolAllocator::INVALID_HANDLE) + { + throw std::runtime_error("Could not allocate message header."); + } + + // Access the message header. + message.m_Header = &m_MessageHeaders[message_header_handle]; + auto header = message.m_Header; + + // Set the payload information. + header->payload_info.device_id = device_id; + header->payload_info.total_size = payload_size; + header->payload_info.offset_in_buffer = offset; + m_UuidGenerator.Generate(header->payload_id); + + // Set the topic. + std::strncpy(header->topic, topic.c_str(), sizeof(header->topic)); + + // Set the trace ID. + std::strncpy(header->trace_id, trace_id, sizeof(header->trace_id)); + + // Set the producer information. + std::strncpy(header->producer_hostname, GetHostName().c_str(), sizeof(header->producer_hostname)); + header->producer_pid = GetProcessId(); + + header->partial_frame_id = 0; + header->start_byte = 0; + header->end_byte = payload_size; + + // Set default values. + header->frame_id = INVALID_FRAME_ID; + header->producer_timestamp = 0; + + return message; +} + +void MessageBroker::PublishMessage(Message &message, bool is_final) +{ + if (message.m_HasBeenPublished) + { + return; + } + + auto topic = std::string_view(message.m_Header->topic); + auto topic_header = m_TopicHeaders.Find(topic); + + if (message.m_Header->frame_id == INVALID_FRAME_ID) + { + // First partial frame. Assign a new frame ID. + message.m_Header->frame_id = topic_header->next_frame_id.fetch_add(1, std::memory_order_relaxed); + message.m_Header->partial_frame_id = 0; + } + else + { + // Not the first partial frame. Use the same frame ID and increment the partial frame ID. + message.m_Header->partial_frame_id++; + } + + // Set the timestamp. + message.m_Header->producer_timestamp = GetTimeStamp(); + + // TODO: put message offsets. + + // Go to synchronization structures and signal them. + // This includes parent topics. + for (std::size_t i = 0; i <= topic.size(); ++i) + { + std::size_t size = topic.size() - i; + + if (i == 0 || topic[size] == '/') + { + auto synchronization = GetSynchronization(topic.substr(0, size)); + + if (synchronization) + synchronization->Signal(); + } + } + + if (!is_final) + { + // Copy the message header since it's gone after publishing. + auto message_header_handle = m_MessageHeaderAllocator.Allocate(); + + if (message_header_handle == PoolAllocator::INVALID_HANDLE) + { + throw std::runtime_error("Could not allocate message header."); + } + + auto new_message_header = &m_MessageHeaders[message_header_handle]; + *new_message_header = *message.m_Header; + message.m_Header = new_message_header; + } + + message.m_HasBeenPublished = is_final; +} + +std::shared_ptr MessageBroker::GetAllocator(int8_t device_id) +{ + if (device_id < -1 || device_id >= MAX_NUM_GPUS) + { + return nullptr; + } + + if (device_id == -1) + { + return m_CpuPayloadAllocator; + } + + return m_GpuPayloadAllocator[device_id]; +} + +std::shared_ptr MessageBroker::GetSynchronization(std::string_view topic) +{ + auto topic_header = m_TopicHeaders.Find(topic); + + if (topic_header == nullptr) + { + return nullptr; + } + + // Look up the synchronization structure (not the shared data). + if (m_Synchronizations.find(topic) == m_Synchronizations.end()) + { + m_Synchronizations[topic] = std::make_shared(topic_header->synchronization); + } + + return m_Synchronizations[topic]; +} diff --git a/catkit_core/MessageBroker.h b/catkit_core/MessageBroker.h new file mode 100644 index 00000000..c547c9b8 --- /dev/null +++ b/catkit_core/MessageBroker.h @@ -0,0 +1,199 @@ +#ifndef MESSAGE_BROKER_H +#define MESSAGE_BROKER_H + +#include "HashMap.h" +#include "Synchronization.h" +#include "FreeListAllocator.h" +#include "PoolAllocator.h" +#include "SharedMemory.h" +#include "CudaSharedMemory.h" +#include "UuidGenerator.h" + +#include +#include + +const char * const MESSAGE_BROKER_VERSION = "0.1"; + +const size_t VERSION_SIZE = 8; +const size_t TOPIC_HASH_MAP_SIZE = 16384; +const size_t TOPIC_MAX_KEY_SIZE = 128; +const size_t TOPIC_MAX_NUM_MESSAGES = 15; +const size_t HOST_NAME_SIZE = 64; +const size_t METADATA_MAX_STRLEN = 16; +const size_t MAX_NUM_DIMENSIONS = 4; +const size_t MAX_NUM_METADATA_ENTRIES = 16; +const size_t MAX_SHARED_MEMORY_ID_SIZE = 64; +const size_t MAX_NUM_GPUS = 8; + +const std::uint64_t INVALID_FRAME_ID = 0xFFFFFFFFFFFFFFFF; + +union MetadataEntry +{ + std::uint64_t integer; + double floating_point; + char string[METADATA_MAX_STRLEN]; +}; + +struct ArrayInfo +{ + char data_type; + char byte_order; + std::uint8_t num_dimensions; + std::uint32_t shape[MAX_NUM_DIMENSIONS]; + std::uint32_t strides[MAX_NUM_DIMENSIONS]; +}; + +struct PayloadInfo +{ + std::int8_t device_id; + std::uint64_t offset_in_buffer; + std::uint64_t total_size; + + ArrayInfo array_info; +}; + +struct MessageHeader +{ + char topic[TOPIC_MAX_KEY_SIZE]; + + Uuid payload_id; + std::uint64_t frame_id; + + Uuid trace_id; + + char producer_hostname[HOST_NAME_SIZE]; + std::uint32_t producer_pid; + std::uint64_t producer_timestamp; + + PayloadInfo payload_info; + + MetadataEntry metadata_entries[MAX_NUM_METADATA_ENTRIES]; + + std::uint16_t partial_frame_id; + std::uint64_t start_byte; + std::uint64_t end_byte; +}; + +struct TopicHeader +{ + std::atomic_uint64_t next_frame_id; + std::uint64_t message_offsets[TOPIC_MAX_NUM_MESSAGES]; + + SynchronizationSharedData synchronization; + + char metadata_keys[METADATA_MAX_STRLEN][MAX_NUM_METADATA_ENTRIES]; + + TopicHeader() = default; + TopicHeader(const TopicHeader &header); + + TopicHeader &operator=(const TopicHeader &header); + +private: + void CopyFrom(const TopicHeader &header); +}; + +struct MessageBrokerHeader +{ + char version[VERSION_SIZE]; + char creator_hostname[HOST_NAME_SIZE]; + std::uint64_t time_of_last_activity; + + char buffer_shared_memory_id[MAX_SHARED_MEMORY_ID_SIZE]; + CudaIpcHandle cuda_ipc_handles[MAX_NUM_GPUS]; +}; + +class MessageBroker; + +class Message +{ + friend class MessageBroker; + +private: + Message(); + +public: + ~Message(); + + const char *GetTopic() const; + + const Uuid &GetPayloadId() const; + const std::uint64_t GetFrameId() const; + + const Uuid &GetTraceId() const; + + const char *GetProducerHostnname() const; + const std::uint32_t GetProducerPid() const; + const std::uint64_t GetProducerTimestamp() const; + + const PayloadInfo &GetPayloadInfo() const; + + const ArrayInfo &GetArrayInfo() const; + void SetArrayInfo(const ArrayInfo &array_info); + + void *GetPayload() const; + size_t GetPayloadSize() const; + + const MetadataEntry &GetMetadataEntry(std::uint8_t metadata_id) const; + void SetMetadataEntry(std::uint8_t metadata_id, std::uint64_t value); + void SetMetadataEntry(std::uint8_t metadata_id, double value); + void SetMetadataEntry(std::uint8_t metadata_id, const char *value); + + const std::uint16_t GetPartialFrameId() const; + + const std::uint64_t GetStartByte() const; + void SetStartByte(const std::uint64_t &start_byte); + + const std::uint64_t GetEndByte() const; + void SetEndByte(const std::uint64_t &end_byte); + +private: + MessageHeader *m_Header; + void *m_Payload; + + bool m_HasBeenPublished; + + std::shared_ptr m_MessageBroker; +}; + +class MessageBroker : std::enable_shared_from_this +{ + friend class Message; + +private: + MessageBroker(); // TODO: Add parameters. + +public: + std::unique_ptr Create(); // TODO: Add parameters. + std::unique_ptr Open(void *metadata_buffer); + + Message PrepareMessage(const std::string &topic, size_t payload_size, int8_t device_id = -1); + Message PrepareMessage(const std::string &topic, Uuid trace_id, size_t payload_size, int8_t device_id = -1); + + void PublishMessage(Message &message, bool is_final = true); + + Message GetNextMessage(const std::string &topic, double timeout_in_seconds); + Message GetMessage(const std::string &topic, size_t frame_id); + +private: + std::shared_ptr GetAllocator(int8_t device_id); + std::shared_ptr GetSynchronization(std::string_view topic); + + MessageBrokerHeader &m_Header; + + HashMap m_TopicHeaders; + PoolAllocator m_MessageHeaderAllocator; + + MessageHeader *m_MessageHeaders; + + std::shared_ptr m_CpuPayloadAllocator; + std::shared_ptr m_CpuPayloadMemory; + + std::shared_ptr m_GpuPayloadAllocator[MAX_NUM_GPUS]; + std::shared_ptr m_GpuPayloadMemory[MAX_NUM_GPUS]; + + UuidGenerator m_UuidGenerator; + + std::map> m_Synchronizations; +}; + +#endif // MESSAGE_BROKER_H diff --git a/catkit_core/SharedMemory.cpp b/catkit_core/SharedMemory.cpp index a469b9c4..c8b8d356 100644 --- a/catkit_core/SharedMemory.cpp +++ b/catkit_core/SharedMemory.cpp @@ -82,7 +82,7 @@ SharedMemory::SharedMemory(const std::string &id, FileObject file, bool is_owner throw std::runtime_error("Something went wrong while mapping shared memory file."); } -void *SharedMemory::GetAddress() +void *SharedMemory::GetAddress(std::size_t offset) { - return m_Buffer; + return static_cast(m_Buffer) + offset; } diff --git a/catkit_core/SharedMemory.h b/catkit_core/SharedMemory.h index aa6764bd..ca78b51c 100644 --- a/catkit_core/SharedMemory.h +++ b/catkit_core/SharedMemory.h @@ -1,6 +1,8 @@ #ifndef SHARED_MEMORY_H #define SHARED_MEMORY_H +#include "Memory.h" + #include #include @@ -21,7 +23,7 @@ typedef int FileObject; #endif -class SharedMemory +class SharedMemory : public Memory { private: SharedMemory(const std::string &id, FileObject file, bool is_owner); @@ -32,7 +34,7 @@ class SharedMemory static std::shared_ptr Create(const std::string &id, size_t num_bytes_in_buffer); static std::shared_ptr Open(const std::string &id); - void *GetAddress(); + void *GetAddress(std::size_t offset = 0) override; private: std::string m_Id; diff --git a/catkit_core/UuidGenerator.cpp b/catkit_core/UuidGenerator.cpp new file mode 100644 index 00000000..80328a02 --- /dev/null +++ b/catkit_core/UuidGenerator.cpp @@ -0,0 +1,21 @@ +#include "UuidGenerator.h" + +UuidGenerator::UuidGenerator() +{ + std::random_device random_device; + + for (size_t i = 0; i < 2; ++i) + { + std::seed_seq seed{random_device(), random_device()}; + m_Engines[i].seed(seed); + } +} + +void UuidGenerator::Generate(Uuid &uuid) +{ + for (size_t i = 0; i < 2; ++i) + { + std::uint64_t value = m_Engines[i](); + *reinterpret_cast(uuid + i * 8) = value; + } +} diff --git a/catkit_core/UuidGenerator.h b/catkit_core/UuidGenerator.h new file mode 100644 index 00000000..87523421 --- /dev/null +++ b/catkit_core/UuidGenerator.h @@ -0,0 +1,19 @@ +#ifndef UUID_GENERATOR_H +#define UUID_GENERATOR_H + +#include + +using Uuid = char[16]; + +class UuidGenerator +{ +public: + UuidGenerator(); + + void Generate(Uuid &uuid); + +private: + std::mt19937_64 m_Engines[2]; +}; + +#endif // UUID_GENERATOR_H