希望能支持Arm, 还有项目不要太监了
网站首页> 文章专栏> 【重磅开源】兰亭集库(yaLanTingLibs)在github上开源了!
“仰观宇宙之大,俯察品类之盛,所以游目骋怀,足以极视听之娱,信可乐也。”--《兰亭集序》
C++20 基础库兰亭集库 https://github.com/alibaba/yalantinglibs (yaLanTingLibs, ya不发音)正式开源!她的名字灵感正是来源于“兰亭集序”,因为兰亭集库是一个C++20 库的集合,里面有平时开发中常用到的库,如协程库(async_simple)、序列化库(struct_pack)和rpc(coro_rpc)库,后面还会不断增加新的基础库如http库,orm等库,总之,兰亭集库的目标是帮助C++用户快速构建高性能C++应用,易用性、性能和安全性是兰亭集库的主要特色!兰亭集库的长期目标是完善C++开发的生态。
因为yaLanTingLibs是基础库的合集,所以里面的每个子库都是可以独立使用的,如果你只需要序列化功能,只需要包含struct_pack的头文件就行,如果你只需要rpc功能,就包含coro_rpc的头文件,如果你只想使用协程库,那就只包含async_simple头文件。
真心希望大家yaLanTingLibs能帮助大家快速开发C++应用,希望大家能积极去使用yaLanTingLibs,感受一下它带来的开发效率和高性能!
yaLanTingLibs对编译器的要求:需要C++20标准,clang13+, gcc10+, msvc2022正在支持当中。推荐使用clang13+
开源是起点而不是终点,我们会长期维护yaLanTingLibs,也会把它作为purecpp社区重点开源项目,每个月会组织和社区的会议,讨论兰亭库新特性支持和未来的工作,欢迎大家加入到兰亭库的维护与开发中来。
前面已经讲到了兰亭集库的主要特色是易用、安全和高性能,这里想通过一些example来展示这些特性。
struct_pack是一个以零成本抽象,高度易用为特色序列化库。只需一行代码即可完成复杂结构体的序列化/反序列化。用户无需定义任何DSL,宏或模板代码,struct_pack可通过编译期反射自动支持对C++结构体的序列化。其性能比protobuf,msgpack高一个数量级(10-50倍,详细可以看benchmark部分)。
以一个简单的对象为例展示struc_pack的基本用法。
struct person {
int64_t id;
std::string name;
int age;
double salary;
};
// 初始化一个person对象
person person1{.id = 1, .name = "hello struct pack", .age = 20, .salary = 1024.42};
// 1行代码序列化
std::vector<char> result = serialize(person1);
// 1行代码反序列化
auto person2 = deserialize<person>(buffer);
assert(person2); //person2.has_value()==true
assert(person2.value()==person1);
有时候只想反序列化对象的某个特定的字段而不是全部,这时候就可以用部分反序列化功能了,这样可以避免全部反序列化,大幅提升效率。
// 只反序列化person的第2个字段
auto name = get_field<person, 1>(buffer.data(), buffer.size());
assert(name); //name.has_value()==true
assert(name.value() == "hello struct pack");
含各种容器的对象序列化
enum class Color { red, black, white };
struct complicated_object {
Color color;
int a;
std::string b;
std::vector<person> c;
std::list<std::string> d;
std::deque<int> e;
std::map<int, person> f;
std::multimap<int, person> g;
std::set<std::string> h;
std::multiset<int> i;
std::unordered_map<int, person> j;
std::unordered_multimap<int, int> k;
std::array<person, 2> m;
person n[2];
std::pair<std::string, person> o;
std::optional<int> p;
};
struct nested_object {
int id;
std::string name;
person p;
complicated_object o;
};
nested_object nested{.id = 2, .name = "tom", .p = {20, "tom"}, .o = {}};
auto buffer = serialize(nested);
auto nested2 = deserialize(buffer.data(), buffer.size());
assert(nested2)
assert(nested2==nested1);
自定义容器的序列化
// We should not inherit from stl container, this case just for testing.
template <typename Key, typename Value>
struct my_map : public std::map<Key, Value> {};
my_map<int, std::string> map1;
map1.emplace(1, "tom");
map1.emplace(2, "jerry");
absl::flat_hash_map<int, std::string> map2 =
{{1, "huey"}, {2, "dewey"}, {3, "louie"},};
auto buffer1 = serialize(map1);
auto buffer2 = serialize(map2);
待序列化的对象已经预先初始化,存储序列化结果的内存已经预先分配。对每个测试用例。我们运行一百万次序列化/反序列化,对结果取平均值。
struct person {
int64_t id;
std::string name;
int age;
double salary;
};
enum Color : uint8_t { Red, Green, Blue };
struct Vec3 {
float x;
float y;
float z;
};
struct Weapon {
std::string name;
int16_t damage;
};
struct Monster {
Vec3 pos;
int16_t mana;
int16_t hp;
std::string name;
std::vector<uint8_t> inventory;
Color color;
std::vector<Weapon> weapons;
Weapon equipped;
std::vector<Vec3> path;
};
struct rect {
int32_t x;
int32_t y;
int32_t width;
int32_t height;
};
Compiler: Alibaba Clang 13
CPU: (Intel(R) Xeon(R) Platinum 8163 CPU @ 2.50GHz)
当对象增加新的字段时,怎么保证兼容新旧对象的解析呢?当用户需要添加字段时,只需要在新对象末尾
增加新的 struct_pack::compatible<T>
字段即可。
以person对象为例:
struct person {
int age;
std::string name;
};
struct person1 {
int age;
std::string name;
struct_pack::compatible<int32_t> id;
struct_pack::compatible<bool> maybe;
};
struct_pack保证这两个类可以通过序列化和反序列化实现安全的相互转换,从而实现了向前/向后的兼容性。
coro_rpc是用C++20开发的基于无栈协程和编译期反射的高性能的rpc库,在单机上echo测试qps达到2000万(详情见benchmark部分) ,性能远高于grpc。然而高性能不是它的主要特色,coro_rpc的主要特色是易用性,免安装,包含头文件就可以用,几行代码就可以完成一个rpc服务器和客户端。
coro_rpc的设计理念是:以易用性为核心,回归rpc本质,让用户专注于业务逻辑而不是rpc框架细节,几行代码就可以完成rpc开发。 rpc的本质是什么?rpc的本质就是一个远程函数,除了rpc底层的网络IO之外,其它的就和普通函数一样。用户不需要关注rpc底层的网络IO、路由、序列化等细节,用户只需要专注于rpc函数的业务逻辑即可,这就是coro_rpc的设计理念,正是基于这一设计理念,coro_rpc提供了非常简单易用的API给用户使用。通过一个例子来看coro_rpc的易用性。
1.定义rpc函数
// rpc_service.hpp
inline std::string echo(std::string str) { return str; }
2.注册rpc函数和启动server
#include "rpc_service.hpp"
#include <coro_rpc/coro_rpc_server.hpp>
int main() {
register_handler<echo>(); // 注册rpc函数
coro_rpc_server server(/*thread_num =*/hardware_concurrency(), /*port =*/9000);
server.start(); // 启动server并阻塞等待
}
对于rpc服务端来说,用户只需要定义rpc函数再启动server即可,不需要关注其它细节,5,6行代码就可以提供一个rpc服务了,是不是很简单!再来看看client是怎么调用hello这个rpc服务的。
#include "rpc_service.hpp"
#include <coro_rpc/coro_rpc_client.hpp>
Lazy<void> test_client() {
coro_rpc_client client;
co_await client.connect("localhost", /*port =*/9000);
auto r = co_await client.call<echo>("hello coro_rpc"); //传参数调用rpc函数
std::cout << r.result.value() << "\n"; //will print "hello coro_rpc"
}
int main() {
syncAwait(test_client());
}
client调用rpc函数也同样简单,5,6行代码就可以实现rpc调用了。 就像调用本地函数一样调用远程rpc函数,在call里面输入函数名字和参数就可以实现远程调用了,非常简单。
相信上面的这个简单的例子已经充分展示了coro_rpc的易用性和特点了,也体现了rpc的本质,即用户可以像调用本地函数那样调用远程函数,用户只需要关注rpc函数的业务逻辑即可。
coro_rpc的接口易用性还体现在rpc函数几乎没有任何限制,你可以定义任意个数和任意类型参数的rpc函数, 参数的序列化和反序列化由rpc库自动完成,用户无需关心,简言之,coro_rpc可以让你像写本地函数一样写rpc函数,像调用本地函数一样调用rpc函数。
// rpc_service.h
// 客户端只需要包含这个头文件即可,无需把rpc的定义暴露给客户端。
void hello(){};
int get_value(int a, int b){return a + b;}
struct person {
int id;
std::string name;
int age;
};
person get_person(person p, int id);
struct dummy {
std::string echo(std::string str) { return str; }
};
// rpc_service.cpp
#include "rpc_service.h"
int get_value(int a, int b){return a + b;}
person get_person(person p, int id) {
p.id = id;
return p;
}
server端
#include "rpc_service.h"
#include <coro_rpc/coro_rpc_server.hpp>
int main() {
register_handler<hello, get_value, get_person>();//注册任意参数类型的普通函数
dummy d{};
register_handler<&dummy::echo>(&d); //注册成员函数
coro_rpc_server server(/*thread_num =*/hardware_concurrency(), /*port =*/9000);
server.start(); // 启动server
}
client端
# include "rpc_service.h"
# include <coro_rpc/coro_rpc_client.hpp>
Lazy<void> test_client() {
coro_rpc_client client;
co_await client.connect("localhost", /*port =*/9000);
//RPC调用
co_await client.call<hello>();
co_await client.call<get_value>(1, 2);
person p{};
co_await client.call<get_person>(p, /*id =*/1);
auto r = co_await client.call<&dummy::echo>("hello coro_rpc");
std::cout << r.result.value() << "\n"; //will print "hello coro_rpc"
}
int main() {
syncAwait(test_client());
}
这里面get_person函数的参数和返回值都是结构体,通过编译期反射的序列化库struct_pack实现自动的序列化和反序列化,用户无感知,省心省力。
Lazy<int> get_coro_value(int val) { co_return val; }
int main() {
register_handler<get_coro_value>();//注册协程函数
coro_rpc_server server(/*thread_num =*/hardware_concurrency(), /*port =*/9000);
server.start();
}
void hello_with_delay(connection<std::string> conn) {
// 在线程或者线程池中处理rpc请求
std::thread([conn]() mutable {
// 处理完成后response结果
conn.response_msg("hello coro_rpc");
}).detach();
}
int main() {
register_handler<hello_with_delay>();
coro_rpc_server server(2, 8801);
server.start();
}
如果你不想使用协程,那么就用经典的异步回调的server
# include <coro_rpc/async_rpc_server.hpp>
inline std::string hello() { return "hello coro_rpc"; }
int main() {
register_handler<hello>();
async_rpc_server server(/*thread_num =*/hardware_concurrency(), /*port =*/9000);
server.start();
}
Intel(R) Xeon(R) Platinum 8163 CPU @ 2.50GHz 96核 OS: Linux version 4.9.151-015.ali3000.alios7.x86_64 编译器:Alibaba Clang13 C++20
编译选项:Release –O3
客户端和服务端都在同一台机器上,使用不同连接数发送请求做echo测试 Pipeline 模式下极限qps 测试 Ping-pong模式下的qps和时延测试
极限qps
pingpong qps
pingpong latency
长尾测试 qps
长尾测试 latency
Newer is Better 是purecpp社区倡导的,新技术带来新的编程思想,新的编程思想带来技术创新,只有通过技术创新才能在性能、易用性上超越经典。兰亭集库(yaLanTingLibs)就是这一倡导的最佳体现。
是因循守旧,止步不前,还是拥抱新技术,走积极进取的创新之路,it's your choice!
真心希望兰亭集库能帮助大家快速开发高性能C++应用,让大家享受到新技术带来的易用和效率方面的好处。
最后希望喜欢和准备使用yaLanTingLibs的用户加入到我们的群参与讨论,新需求,未来的方向都需要你们来规划,yaLanTingLibs是大家的,是社区的,希望大家能积极参与到这个开源项目中来。
地址: www.purecpp.cn
转载请注明出处!
希望能支持Arm, 还有项目不要太监了
暂时没有精力去维护两个仓库,你们可以自己转过去;
项目会长期维护,purecpp社区会重点支持兰亭集库。
purecpp
一个很酷的modern c++开源社区
purecpp社区自2015年创办以来,以“Newer is Better”为理念,相信新技术可以改变世界,一直致力于现代C++研究、应用和技术创新,期望通过现代C++的技术创新来提高企业生产力和效率。
社区坚持只发表原创技术文章,已经累计发表了一千多篇原创C++技术文章;
组织了十几场的C++沙龙和C++大会,有力地促进了国内外C++开发者之间的技术交流;
开源了十几个现代C++项目,被近百家公司所使用,有力地推动了现代C++在企业中的应用。
期待更多的C++爱好者能参与到社区C++社区的建设中来,一起为现代C++开源项目添砖加瓦,一起完善C++基础设施和生态圈。
微信公众号:purecpp, 社区邮箱: purecpp@163.com
好库,不过能不能在`gitee`上放一个地址.
`github`有时访问不了.