当前位置: 首页 > article >正文

protobuf, rpc, 火焰图

文章目录

  • 前言
  • 一、生成火焰图
  • 二、protobuf
    • 标量值类型
    • 变长整形
    • 有符号整数
    • 非变长整数
    • 长度限定记录
    • 可选和重复元素
    • 打包重复字段
    • 映射
  • grpc
    • 2. CMakeLists.txt
  • references


前言

protobuf: 一种将结构化数据进行序列化和反序列化的协议,提供IDL工具,生成接口代码,支持多种语言。
rpc: 类似http的请求相应模式,可跨设备提供远程接口调用功能,其接口可通过protobuf定义。

利用perf生成火焰图,分析程序性能


一、生成火焰图

perf record -F 99 -g --pid $PID -- sleep 30
  • -F 99:设置采样频率为每秒99次(可以根据需要调整)。
  • –pid $PID:指定要监控的进程ID。
  • – sleep 30:表示监控30秒。
git clone https://github.com/brendangregg/FlameGraph.git
perf script | ./FlameGraph/stackcollapse-perf.pl | ./FlameGraph/flamegraph.pl > flamegraph.svg

flamegraph.svg

二、protobuf

  1. 编译源码:
git clone https://github.com/protocolbuffers/protobuf.git
cd protobuf
git submodule update --init --recursive

cmake . -DCMAKE_CXX_STANDARD=14
cmake --build .
  1. 定义.proto文件
syntax = "proto2";

package tutorial;

message Person {
  optional string name = 1;
  optional int32 id = 2;
  optional string email = 3;

  enum PhoneType {
    PHONE_TYPE_UNSPECIFIED = 0;
    PHONE_TYPE_MOBILE = 1;
    PHONE_TYPE_HOME = 2;
    PHONE_TYPE_WORK = 3;
  }

  message PhoneNumber {
    optional string number = 1;
    optional PhoneType type = 2 [default = PHONE_TYPE_HOME];
  }

  repeated PhoneNumber phones = 4;
}

message AddressBook {
  repeated Person people = 1;
}
  1. 编译.proto文件生成接口代码
protoc -I=$SRC_DIR --cpp_out=$DST_DIR $SRC_DIR/addressbook.proto
  1. CMakeLists.txt
cmake_minimum_required(VERSION 3.10)

project(protobuf-test)

find_package(protobuf CONFIG REQUIRED)
 
if(protobuf_VERBOSE)
  message(STATUS "Using Protocol Buffers ${protobuf_VERSION}")
endif()

set(CMAKE_INCLUDE_CURRENT_DIR TRUE)

add_executable(${PROJECT_NAME} main.cpp addressbook.pb.cc)
target_include_directories(${PROJECT_NAME} PUBLIC 
    install/usr/local/include
)
target_link_libraries(${PROJECT_NAME}
    protobuf::libprotobuf
)
#include <iostream>
#include <fstream>
#include <string>
#include "addressbook.pb.h"

using namespace std;

void list_people(const tutorial::AddressBook& address_book)
{
    for (int i = 0; i< address_book.people_size(); ++i) {
        const tutorial::Person& person = address_book.people(i);
        cout << "Person ID: " << person.id() << endl;
        cout << " Name: " << person.name() << endl;
        if (person.has_email()) {
            cout << " E-mail address: " << person.email() << endl;
        }
        for (int j = 0; j < person.phones_size(); ++j) {
            const tutorial::Person::PhoneNumber& phone_number = person.phones(j);

            switch (phone_number.type()) {
                case tutorial::Person::PHONE_TYPE_MOBILE:
                cout << "  Mobile phone #: ";
                break;
                case tutorial::Person::PHONE_TYPE_HOME:
                cout << "  Home phone #: ";
                break;
                case tutorial::Person::PHONE_TYPE_WORK:
                cout << "  Work phone #: ";
                break;
            }
            cout << phone_number.number() << endl;
        }
    }
}

void PromptForAddress(tutorial::Person* person)
{
    cout << "enter person id number: ";
    int id;
    cin >> id;
    person->set_id(id);
    cin.ignore(256, '\n');
    cout << "enter name: ";
    getline(cin, *person->mutable_name());
    string email;
    getline(cin, email);
    if (!email.empty())
    {
        person->set_email(email);
    }
    while (true)
    {
        cout << "enter a ph num: ";
        string number;
        getline(cin, number);
        if (number.empty()) break;
        tutorial::Person::PhoneNumber* phone_number = person->add_phones();
        phone_number->set_number(number);
        cout << "is this a mobile, home, or work phone?";
        string type;
        getline(cin, type);
        if (type == "mobile") {
            phone_number->set_type(tutorial::Person::PHONE_TYPE_MOBILE);
        } else if (type == "home") {
            phone_number->set_type(tutorial::Person::PHONE_TYPE_HOME);
        } else if (type == "work") {
            phone_number->set_type(tutorial::Person::PHONE_TYPE_WORK);
        } else {
            cout << "Unknown type. using default." << endl;
        }
    }

    
}

int main(int argc, char* argv[])
{
    // Verify that the version of the library that we linked against is
    // compatible with the version of the headers we compiled against.
    GOOGLE_PROTOBUF_VERIFY_VERSION;

    if (argc != 2)
    {
        cerr << "Usage: " << argv[0] << " ADDRESS_BOOK_FILE" << endl;
        return -1;
    }
    tutorial::AddressBook address_book;
    {
        fstream input(argv[1], ios::in | ios::binary);
        if (!input) {
            cout << argv[1] << ": File not found. Create a new file." << endl;
        } else if (!address_book.ParseFromIstream(&input)) {
            cerr << "Failed to parse address book." << endl;
            return -1;
        }
    }
    PromptForAddress(address_book.add_people());
    {
        fstream output(argv[1], ios::out | ios::trunc | ios::binary);
        std::stringstream ss;
        std::vector<char> buf;
        if (!address_book.SerializeToOstream(&output)) {
            cerr << "Failed to write address book." << endl;
            return -1;
        }
        string str;
        if (!address_book.SerializeToString(&str))
        {
            cerr << "Failed to write address book." << endl;
            return -1;
        }
        for (int i = 0; i < str.size(); ++i)
        {
            printf("%x ", *(unsigned char*)&str[i]);
        }
        cout << "\n";
    }
    list_people(address_book);

    // Optional:  Delete all global objects allocated by libprotobuf.
    google::protobuf::ShutdownProtobufLibrary();
}

标量值类型

.proto 类型注释C++ 类型
doubledoubledouble
floatfloatfloat
int32使用可变长度编码。对编码负数效率低下——如果您的字段可能包含负值,请改用 sint32。int32
int64使用可变长度编码。对编码负数效率低下——如果您的字段可能包含负值,请改用 sint64。int64
uint32使用可变长度编码。uint32
uint64使用可变长度编码。uint64
sint32使用可变长度编码。有符号整数类型。这些类型比常规 int32 更有效地编码负数。int32
sint64使用可变长度编码。有符号整数类型。这些类型比常规 int64 更有效地编码负数。int64
fixed32始终为四个字节。如果值通常大于 228,则比 uint32 更有效。uint32
fixed64始终为八个字节。如果值通常大于 256,则比 uint64 更有效。uint64
sfixed32始终为四个字节。int32
sfixed64始终为八个字节。int64
boolbool
string 字符串必须始终包含 UTF-8 编码或 7 位 ASCII 文本,并且长度不能超过 232。string
bytes 可以包含任何任意字节序列,长度不超过 232。string

标量之类型 https://protobuf.com.cn/programming-guides/proto3/

编码 https://protobuf.com.cn/programming-guides/encoding/

变长整形

可变宽度整数,或 变长整数,是线格式的核心。它们允许使用 1 到 10 个字节对无符号 64 位整数进行编码,其中较小的值使用较少的字节。

变长整数中的每个字节都有一个 延续位,指示其后面的字节是否属于该变长整数。这是字节的 最高有效位 (MSB)(有时也称为 符号位)。较低的 7 位是有效负载;生成的整数是通过将组成字节的 7 位有效负载拼接在一起构建的。

因此,例如,以下是数字 1,编码为 01——它是一个字节,因此 MSB 未设置

0000 0001
^ msb

以下是 150,编码为

低字节    高字节
10010110 00000001
^ msb    ^ msb
10010110 00000001        // Original inputs.
 低字节     高字节
 0010110  0000001        // Drop continuation bits.
 p[0]      p[1]
 uint64 = p[0]&0x7f + p[1]&0xf7 * 128UL = 22 + 128 = 150

协议缓冲区消息是一系列键值对。消息的二进制版本仅使用字段的编号作为键——每个字段的名称和声明类型只能在解码端通过引用消息类型的定义(即 .proto 文件)来确定。Protoscope 无法访问此信息,因此它只能提供字段编号。

当消息被编码时,每个键值对都会转换为一个 记录,该记录由字段编号、线类型和有效负载组成。线类型告诉解析器后面的有效负载有多大。这允许旧解析器跳过它们不理解的新字段。这种类型的方案有时称为 标记-长度-值 或 TLV。

有六种线类型:VARINT、I64、LEN、SGROUP(已弃用)、EGROUP(已弃用) 和 I32

ID名称用于
0VARINTint32、int64、uint32、uint64、sint32、sint64、bool、enum
1I64fixed64、sfixed64、double
2LENstring、bytes、嵌入式消息、打包重复字段
5I32fixed32、sfixed32、float

记录的“标记”编码为一个变长整数,该整数由字段编号和线类型通过公式 (field_number << 3) | wire_type 形成。换句话说,在解码表示字段的变长整数后,低 3 位告诉我们线类型,其余整数告诉我们字段编号。

现在让我们再次查看我们的简单示例。您现在知道流中的第一个数字始终是变长整数键,这里它是 0x08,或者(删除 MSB)

key的值:
0000 1000
 000 1000
 ^^^ ^ field_number
      ^^^ wire_type

所以 字段编号为1
类型为 VARINT

您取最后三位以获取线类型 (0),然后右移三位以获取字段编号 (1)。Protoscope 将标记表示为整数后跟冒号和线类型,因此我们可以将上述字节写为 1:VARINT。

因为线类型是 0 或 VARINT,所以我们知道我们需要解码一个变长整数以获取有效负载。如上所述,字节 0x9601 变长整数解码为 150,从而给出我们的记录。我们可以用 Protoscope 将其写成 1:VARINT 150。

布尔值和枚举都像 int32 一样编码。特别是,布尔值始终编码为 0001。在 Protoscope 中,false 和 true 是这些字节字符串的别名。

有符号整数

正如您在上一节中看到的,与线类型 0 关联的所有协议缓冲区类型都编码为变长整数。但是,变长整数是无符号的,因此不同的有符号类型,sint32 和 sint64 与 int32 或 int64 相比,对负整数的编码方式不同。

intN 类型将负数编码为二进制补码,这意味着作为无符号 64 位整数,它们设置了最高位。结果,这意味着必须使用 所有十个字节。例如,-2 由 protoscope 转换为

11111110 11111111 11111111 11111111 11111111
11111111 11111111 11111111 11111111 00000001

这是 2 的二进制补码,在无符号算术中定义为~0 - 2 + 1,其中~0 是全为 1 的 64 位整数。理解为什么这会产生这么多 1 是一项有益的练习。

sintN 使用“ZigZag”编码而不是二进制补码来编码负整数。正整数p 编码为2 * p(偶数),而负整数n 编码为2 * |n| - 1(奇数)。因此,编码在正数和负数之间“锯齿形”变化。例如

带符号原始值编码为
00
-11
12
-23
0x7fffffff0xfffffffe
-0x800000000xffffffff

“ZigZag”:

整数: 左移动一位

负数: ~(左移一位)

非变长整数

非 varint 数值类型很简单 - double 和fixed64 的线类型为I64,它告诉解析器预期一个固定的八字节数据块。我们可以通过编写5: 25.4 来指定double 记录,或通过6: 200i64 来指定fixed64 记录。在这两种情况下,省略显式线类型都意味着I64 线类型。

类似地,float 和fixed32 的线类型为I32,它告诉解析器预期四个字节。这些类型的语法包括添加i32 后缀。25.4i32 将发出四个字节,200i32 也是如此。标签类型被推断为I32。

长度限定记录

message Test2 {
  optional string b = 2;
}

字段b 的记录是一个字符串,字符串是LEN 编码的。如果我们将b 设置为"testing",则将其编码为一个LEN 记录,字段号为 2,包含 ASCII 字符串"testing"。

12 07 [74 65 73 74 69 6e 67]

可选和重复元素

message Test4 {
  optional string d = 4;
  repeated int32 e = 5;
}

4: {"hello"}
5: 1
5: 2
5: 3

打包重复字段

映射

映射字段只是特殊类型的重复字段的简写。如果我们有

message Test6 {
  map<string, int32> g = 7;
}

这实际上与以下内容相同

message Test6 {
  message g_Entry {
    optional string key = 1;
    optional int32 value = 2;
  }
  repeated g_Entry g = 7;
}

因此,映射的编码方式与repeated 消息字段完全相同:作为一系列LEN 类型的记录,每个记录有两个字段。

grpc

https://grpc.io/docs/languages/cpp/basics/
https://github.com/grpc/grpc/tree/master/examples/cpp/route_guide

2. CMakeLists.txt

cmake_minimum_required(VERSION 3.8)

project(RouteGuide C CXX)

foreach(__prefix_path__  absl c-ares grpc protobuf re2 utf8_range)
    list(APPEND CMAKE_PREFIX_PATH ${CMAKE_CURRENT_SOURCE_DIR}/../../thirdparty/grpc/lib/cmake)
endforeach()

include(/home/igs/Projects/grpc/examples/cpp/cmake/common.cmake)

# Find absl package
find_package(absl CONFIG REQUIRED)

# Proto file
get_filename_component(rg_proto "${CMAKE_CURRENT_SOURCE_DIR}/route_guide.proto" ABSOLUTE)
get_filename_component(rg_proto_path "${rg_proto}" PATH)

# Generated sources
set(rg_proto_srcs "${CMAKE_CURRENT_BINARY_DIR}/route_guide.pb.cc")
set(rg_proto_hdrs "${CMAKE_CURRENT_BINARY_DIR}/route_guide.pb.h")
set(rg_grpc_srcs "${CMAKE_CURRENT_BINARY_DIR}/route_guide.grpc.pb.cc")
set(rg_grpc_hdrs "${CMAKE_CURRENT_BINARY_DIR}/route_guide.grpc.pb.h")
add_custom_command(
      OUTPUT "${rg_proto_srcs}" "${rg_proto_hdrs}" "${rg_grpc_srcs}" "${rg_grpc_hdrs}"
      COMMAND ${_PROTOBUF_PROTOC}
      ARGS --grpc_out "${CMAKE_CURRENT_BINARY_DIR}"
        --cpp_out "${CMAKE_CURRENT_BINARY_DIR}"
        -I "${rg_proto_path}"
        --plugin=protoc-gen-grpc="${_GRPC_CPP_PLUGIN_EXECUTABLE}"
        "${rg_proto}"
      DEPENDS "${rg_proto}")

# Include generated *.pb.h files
include_directories("${CMAKE_CURRENT_BINARY_DIR}")

# rg_grpc_proto
add_library(rg_grpc_proto
  ${rg_grpc_srcs}
  ${rg_grpc_hdrs}
  ${rg_proto_srcs}
  ${rg_proto_hdrs})
target_link_libraries(rg_grpc_proto
  absl::absl_log
  ${_REFLECTION}
  ${_GRPC_GRPCPP}
  ${_PROTOBUF_LIBPROTOBUF})

# Targets route_guide_(client|server)
foreach(_target route_guide_server route_guide_client route_guide_callback_server route_guide_callback_client)
  add_executable(${_target}
    "${_target}.cc" helper.cc)
  target_link_libraries(${_target}
    rg_grpc_proto
    absl::flags_parse
    absl::absl_log
    absl::log_initialize
    absl::log_globals
    ${_REFLECTION}
    ${_GRPC_GRPCPP}
    ${_PROTOBUF_LIBPROTOBUF})
endforeach()

references

https://protobuf.dev/programming-guides/proto3/

https://cloud.tencent.com/developer/article/2463651
https://grpc.io/docs/languages/cpp/basics/
https://github.com/grpc/grpc/tree/master/examples/cpp/route_guide


http://www.kler.cn/a/580057.html

相关文章:

  • Kubernetes(K8s)集群中使用 GPU
  • C++零基础LeetCode热题100- 49.字母异位词分组
  • Linux提权-04 capabilities
  • 《 C++ 点滴漫谈: 三十 》高手写 C++,参数这样传才高效!你真的用对了吗?
  • UI自动化:Selenium常规的页面元素定位方法
  • 【漫话机器学习系列】123.感知机学习(Perceptron Learning)
  • 执行npm install 时,是如何将依赖包下载下来的。
  • 服务器磁盘占用率过高解决方案
  • 修复ubuntu下找不到音频设备的问题
  • docker修改daemon.json文件后无法启动
  • Zemax 中的 CAD 文件性能比较
  • 隧道定向号角喇叭为隧道安全保驾护航
  • 腾讯元宝:AI 时代的快速论文阅读助手
  • Windows 图形显示驱动开发-WDDM 3.2-本机 GPU 围栏对象(五)
  • 深度学习与大模型基础-向量
  • LeetCode 解题思路 14(Hot 100)
  • 探讨消息队列系统:AWS SQS vs. Apache Kafka
  • 华为hcia——Datacom实验指南——三层交换和ARP的工作原理
  • 【Academy】Web 缓存中毒 ------ Web cache poisoning
  • 关于ModbusTCP/RTU协议转Ethernet/IP(CIP)协议的方案