Overview
In modern software development, efficient processing and transmission of data are crucial. Cista is a high-performance, zero-copy serialization and reflection library designed for C++17, addressing the serialization and deserialization of complex data structures in a concise and efficient manner.
Simplistic and Efficient Design Philosophy
The design philosophy of Cista emphasizes simplicity and high performance. It is a single-header library with no external dependencies, meaning developers can easily integrate it into their projects. Additionally, Cista does not require preprocessor macros or source code generation mechanisms, making it more flexible and convenient to use. It directly uses native structures for data exchange without additional conversion steps, thereby minimizing processing overhead.
Powerful Features
Cista supports complex data structures, including cyclic references and recursive data structures. This means developers can easily handle nested objects without worrying about performance issues. It also provides a way to serialize data directly to the file system without intermediate buffers, which can save up to 50% of memory. Furthermore, Cista ensures the safety of data processing through continuous fuzz testing using LLVM’s LibFuzzer.
Code Examples
Basic Serialization and Deserialization
#include <iostream>
#include <vector>
#include "cista.h"
// Define a simple structure
struct Person {
int age;
std::string name;
std::vector<std::string> hobbies;
};
// Register the structure for serialization
CISTA_DEFINE_STRUCT(Person, age, name, hobbies);
int main() {
// Create a Person object
Person person{30, "Alice", {"reading", "swimming", "coding"}};
// Serialize to byte vector
auto serialized = cista::serialize(person);
// Deserialize
auto deserialized = cista::deserialize<Person>(serialized);
// Validate deserialization results
std::cout << "Age: " << deserialized->age << std::endl;
std::cout << "Name: " << deserialized->name << std::endl;
std::cout << "Hobbies: ";
for (const auto&& hobby : deserialized->hobbies) {
std::cout << hobby << " ";
}
std::cout << std::endl;
return 0;
}
File Serialization
#include <iostream>
#include <fstream>
#include "cista.h"
struct Config {
int port;
std::string hostname;
bool enable_logging;
std::vector<std::string> allowed_users;
};
CISTA_DEFINE_STRUCT(Config, port, hostname, enable_logging, allowed_users);
int main() {
// Create a configuration object
Config config{8080, "example.com", true, {"user1", "user2", "admin"}};
// Serialize to file
{
std::ofstream file("config.bin", std::ios::binary);
cista::serialize_to(file, config);
}
// Deserialize from file
Config loaded_config;
{
std::ifstream file("config.bin", std::ios::binary);
loaded_config = cista::deserialize_from<Config>(file);
}
// Use the deserialized configuration
std::cout << "Port: " << loaded_config.port << std::endl;
std::cout << "Hostname: " << loaded_config.hostname << std::endl;
std::cout << "Logging enabled: " << std::boolalpha << loaded_config.enable_logging << std::endl;
std::cout << "Allowed users: ";
for (const auto&& user : loaded_config.allowed_users) {
std::cout << user << " ";
}
std::cout << std::endl;
return 0;
}
Zero-Copy Serialization
#include <iostream>
#include "cista.h"
// Using Cista's zero-copy feature
struct ZeroCopyData {
cista::raw::string name;
cista::raw::vector<int> values;
};
CISTA_DEFINE_STRUCT(ZeroCopyData, name, values);
int main() {
// Create data
ZeroCopyData data{cista::raw::string("Test"), {1, 2, 3, 4, 5}};
// Serialize
auto buf = cista::serialize(data);
// Zero-copy deserialization - directly access data in the serialized buffer
auto deserialized = cista::raw::deserialize<ZeroCopyData>(buf);
// Note: No data copying here, directly accessing the raw buffer
std::cout << "Name: " << deserialized->name << std::endl;
std::cout << "Values: ";
for (int value : deserialized->values) {
std::cout << value << " ";
}
std::cout << std::endl;
return 0;
}
Complex Data Structures
#include <iostream>
#include <map>
#include "cista.h"
// Define data containing nested structures
struct Address {
cista::raw::string street;
cista::raw::string city;
int zip_code;
};
CISTA_DEFINE_STRUCT(Address, street, city, zip_code);
struct Employee {
cista::raw::string name;
int age;
Address address;
cista::raw::map<cista::raw::string, int> skills; // Skills and their proficiency
};
CISTA_DEFINE_STRUCT(Employee, name, age, address, skills);
int main() {
// Create a complex object
Address addr{"123 Main St", "Springfield", 12345};
Employee emp{
"John Doe",
35,
addr,
{{"C++", 90}, {"Python", 80}, {"JavaScript", 70}}
};
// Serialize
auto serialized = cista::serialize(emp);
// Deserialize
auto deserialized = cista::raw::deserialize<Employee>(serialized);
// Access data
std::cout << "Employee: " << deserialized->name << std::endl;
std::cout << "Age: " << deserialized->age << std::endl;
std::cout << "Address: " << deserialized->address.street << ", "
<< deserialized->address.city << ", "
<< deserialized->address.zip_code << std::endl;
std::cout << "Skills:" << std::endl;
for (const auto&& [skill, level] : deserialized->skills) {
std::cout << " " << skill << ": " << level << "%" << std::endl;
}
return 0;
}
Performance Optimization Example
#include <iostream>
#include <chrono>
#include <vector>
#include "cista.h"
// Performance testing structure
struct PerformanceData {
int id;
cista::raw::string name;
double values[100]; // Fixed-size array for performance improvement
};
CISTA_DEFINE_STRUCT(PerformanceData, id, name, values);
// Test serialization performance
void performance_test() {
const int num_objects = 10000;
std::vector<PerformanceData> data_list;
// Prepare test data
for (int i = 0; i < num_objects; ++i) {
PerformanceData data;
data.id = i;
data.name = cista::raw::string("Object_" + std::to_string(i));
for (int j = 0; j < 100; ++j) {
data.values[j] = i * 100 + j;
}
data_list.push_back(std::move(data));
}
// Measure serialization time
auto start = std::chrono::high_resolution_clock::now();
auto serialized = cista::serialize(data_list);
auto end = std::chrono::high_resolution_clock::now();
auto duration = std::chrono::duration_cast<std::chrono::milliseconds>(end - start);
std::cout << "Serialized " << num_objects << " objects in "
<< duration.count() << " ms" << std::endl;
std::cout << "Serialized size: " << serialized.size() << " bytes" << std::endl;
// Measure deserialization time
start = std::chrono::high_resolution_clock::now();
auto deserialized = cista::raw::deserialize<std::vector<PerformanceData>>(serialized);
end = std::chrono::high_resolution_clock::now();
duration = std::chrono::duration_cast<std::chrono::milliseconds>(end - start);
std::cout << "Deserialized " << num_objects << " objects in "
<< duration.count() << " ms" << std::endl;
// Verify data integrity
if (deserialized->size() == num_objects) {
std::cout << "Data integrity verified" << std::endl;
} else {
std::cout << "Data integrity check failed" << std::endl;
}
}
int main() {
performance_test();
return 0;
}
Compilation and Usage
Cista is a single-header library that is very easy to use:
- Download the Cista header file:
wget https://raw.githubusercontent.com/felixguendling/cista/master/include/cista.h
- Include the header file in your code:
#include "cista.h"
- Ensure to use C++17 or higher standard during compilation:
g++ -std=c++17 -O3 -I. main.cpp -o main
Performance Advantages
The performance advantages of Cista are primarily reflected in the following aspects:
- Zero-Copy Serialization: Cista can operate directly on the serialized buffer, avoiding unnecessary data copying.
- Memory-Mapped Support: Cista supports serializing data directly to memory-mapped files, further enhancing I/O performance.
- Efficient Binary Format: Cista uses a compact binary format, reducing the size of serialized data.
- Optimized Data Layout: Cista automatically optimizes the memory layout of data structures, improving cache utilization.
Conclusion
Cista is a powerful and efficient C++ serialization library, particularly suitable for scenarios requiring high-performance data processing. Its zero-copy feature, simple API design, and excellent performance make it an ideal choice for C++ developers. Whether for simple configuration storage or complex data exchange needs, Cista provides outstanding solutions.
Through the above code examples, we can see the ease of use and powerful features of Cista. With just simple structure definitions and a few lines of code, efficient data serialization and deserialization can be achieved. For performance-driven C++ developers, Cista is definitely a library worth trying.