sylar高性能服务器-日志(P57-P60)内容记录

文章目录

    • P57-P60:序列化模块
      • Varint(编码)
      • Zigzag(压缩)
      • class ByteArray
        • Node(链表结构)
        • 成员变量
        • 构造函数
        • 写入
        • 读取
        • setPosition
        • addCapacity
      • 测试

P57-P60:序列化模块

​ 序列化模块通常用于将数据转换为可以在网络上传输的格式,或者将接收到的网络数据反序列化为程序内部可用的格式。这个模块可以帮助简化网络通信的数据处理过程,提高服务器的性能和可维护性。

sylar设计的的序列化模块底层使用链表的形式存储数据,这样可以节省内存,管理内存碎片。该模块支持所有基本类型数据的写入和读取,可以选择固定字节长度或可变字节长度写入,使用可变字节长度时,使用Zigzag将有符号整型压缩后再进行Varint编码,这样能够节省大量内存空间。再写入数据时,将数据写入链表的最后一个节点中,若写不下时,创建新的节点继续写入数据。

Varint(编码)

Varint是一种使用一个或多个字节序列化整数的方法,会把整数编码为变长字节。对于32位整型数据经过Varint编码后需要10个字节。在实际场景中小数字的使用率远远多于大数字,因此通过Varint编码对于大部分场景都可以起到很好的压缩效果。

Varint 的编码规则如下:

  1. 对于一个整数,用 7 位来存储整数的数值,最高位用来表示是否还有后续字节,1 表示还有,0 表示结束。
  2. 如果整数的数值小于 128,那么只需要一个字节就可以存储,直接将整数的数值存储在最低的 7 位中,最高位为 0。
  3. 如果整数的数值大于等于 128,那么需要多个字节来存储,每个字节的最高位都为 1,其余 7 位用来存储整数的数值。除了最后一个字节,其他字节的最高位为 1,表示还有后续字节。

举个例子

  • 整数300 --二进制–> 100101100
  • 从最低位(右边开始)按7位分组 10 0101100
  • 第一个字节:10101100
  • 第二个字节:0000010
  • 结果:0xAC 0x02

实现代码

void EncodeVarint(uint32_t value) {// 32位的数据类型最多需要5个字节,如果是64位则需要10个字节uint8_t temp[5];uint8_t i = 0;while(value > 127) {// 取低7位保持不变 将最高位设置为1,表示后续还有字节temp[i++] = (value & 0x7f) | 0x80;// 右移7位value >>= 7;}// 存储最后一个小于127的剩余值temp[i] = value;write(tmp, i);
}

(value & 0x7f):取低7位保持不变

| 0x80:将最高位设置为1,表示后续还有字节

 // value & 0x7f10101101 (173)
& 01111111 (0x7f)
-----------00101101// | 0x8000101101
| 10000000 (0x80)
-----------10101101

Zigzag(压缩)

Zigzag算法将有符号负整数转为正数,这样能够节省字节,因为负数的二进制位几乎全为1。

Zigzag编码将正数映射到偶数,负数映射到奇数。这种映射的目的是为了利用无符号整数的变长编码特性,使得负数和正数都能够通过相同的编码方式进行存储和传输。

编码

static uint64_t EncodeZigzag64(const uint64_t& v) {if(v < 0) {return ((uint64_t)(-v))  * 2 - 1;} else {return v * 2;}
}

解码

static int64_t DecodeZigzag64(const uint64_t& v) {return (v >> 1) ^ -(v & 1);
}

class ByteArray

Node(链表结构)

所有的数据都存在这个链表结构中,在堆区开辟内存存储在ptr中;size为一个节点大小,一般设置为一个页面大小4KB;next指向下一个节点。

struct Node {Node(size_t s);Node();~Node();char* ptr;Node* next;size_t size;
};
成员变量
// 内存块的大小
size_t m_baseSize;
// 当前操作的位置
size_t m_position;
// 总容量
size_t m_capacity;
// 当前数据的大小
size_t m_size;
// 字节序,默认大端
int8_t m_endian;
// 当一个内存块指针
Node* m_root;
// 当前操作的内存块指针
Node* m_cur;
构造函数
ByteArray::ByteArray(size_t base_size): m_baseSize(base_size), m_position(0), m_capacity(base_size), m_size(0), m_endian(SYLAR_BIG_ENDIAN), m_root(new Node(base_size)), m_cur(m_root) {}
写入

在一个ByteArray对象中,数据都是存储在独立的节点中,当一个节点存满时让指针指向下一个节点继续存储需要写入的数据。所以写入数据我们只需要知道

  • 写入数据大小
  • 当前节点中剩余可写入的容量
void ByteArray::write(const void* buf, size_t size) {// 如果写入数据为0,直接返回if(size == 0) {return;}// 根据传入的size设置写入这些数据需要的容量addCapacity(size);// 记录当前节点已经写入的大小size_t npos = m_position % m_baseSize;// 计算当前节点剩余的可写入空间大小。size_t ncap = m_cur->size - npos;// 记录当前要写入数据的偏移量size_t bpos = 0;// 只要还有数据待写入while(size > 0) {// 剩余容量足够if(ncap >= size) {// 将要写入的数据拷贝到当前节点中的缓冲区中。memcpy(m_cur->ptr + npos, (const char*)buf + bpos, size);// 如果当前节点已经写满了(当前节点的偏移量加上写入的数据大小等于当前节点的容量大小)if(m_cur->size == (npos + size)) {// 移向下一个节点m_cur = m_cur->next;}// 更新当前位置m_position += size;// 更新缓冲区的偏移量bpos += size;// 将剩余要写入的数据大小置为0,表示所有数据已经写入完毕size = 0;// 如果当前节点剩余的可写入空间小于要写入的数据大小} else { // 将当前节点剩余的可写入空间大小的数据拷贝到当前节点的缓冲区中memcpy(m_cur->ptr + npos, (const char*)buf + bpos, ncap);// 更新当前位置m_position += ncap;// 更新缓冲区的偏移量bpos += ncap;// 更新剩余要写入的数据大小size -= ncap;// 当前节点已经满了,移动到下一个节点m_cur = m_cur->next;// 更新当前节点剩余的可写入空间大小ncap = m_cur->size;// 新的起点,起始为0npos = 0;}}// 更新 ByteArray 对象的大小 m_size,如果当前位置大于原来的大小,则更新为当前位置if(m_position > m_size) {m_size = m_position;}
}

举个例子

假设我们要往一个ByteArray对象中写入下列数据

void* buf = "Hello,World!";
size_t size = 12;
  1. 首先判断写入数据不为空
  2. 调用addCapacity函数确保对象有足够的容量存储要写入的数据
  3. 假设当前节点还未写入过数据,那么几个变量如下
    • npos = 0
    • ncap = 8 (假设一个节点8字节,意思我这个节点还可以写入8字节的数据)
    • bpos = 0,记录当前写入数据的偏移量,我们要写入的数据是存放在缓冲区中,它的作用就是告诉我们从缓冲区哪里开始把数据拿出来,一开始肯定从头开始读,所以初始化为0
  4. ncap = 0 < size = 12,所以进入else分支,因为一个节点只有8个字节容量,我们要写入的数据有12字节
  5. 从buf中获得8个字节的数据,更新当前节点中最后写入的位置m_position + 8,缓冲区的偏移量bpos + 8,剩余要写入的数据为12 - 8 = 4,(已写入[Hello,Wo])
  6. 当前节点已经满了,移动下一个节点,更新ncap剩余写入空间为8,新的写入起点npos = 0
  7. 然后才是进入if分支,把剩余的4个字节写入([rld!]),跳出循环
  8. 检查当前位置是否大于 m_size,显然是,因为当前位置为12,而原来的 m_size 为0,所以更新 m_size 为12。这个m_size就是整个Bytearray对象含有数据的总大小。
读取

上面写入看懂了,读取也是差不多的。

两个read版本的区别在于读取完毕后是否改变m_position的值,比如第一个read,如果当前位置在第3个节点,读完后可能就在第5个节点了,而第二个read就不会改变。

会改变m_position

void ByteArray::read(void* buf, size_t size) {// size是我们想要读取的数据长度,getReadSize()返回m_size - m_position,也就是在当期操作位置后面还有多少数据能读// 如果要读的数据长度已经超出了ByteArray对象剩余可读的数据长度,抛出错误if(size > getReadSize()) {throw std::out_of_range("not enough len");}// 获取当前节点的位置,m_position是一直累加的,所以要对每一个节点的大小取余才能获取在当前节点中的位置size_t npos = m_position % m_baseSize;// 剩余可读容量size_t ncap = m_cur->size - npos;// buf偏移量size_t bpos = 0;while(size > 0) {// 该节点剩余的位置比要读的数据多if(ncap >= size) {// 将剩余的数据都读到buf中memcpy((char*)buf + bpos, m_cur->ptr + npos, size);// 若正好读完这个节点,则跳到下一个节点,不做这一步也可以,下一次再读可以在else分支里面跳if(m_cur->size == (npos + size)) {m_cur = m_cur->next;}m_position += size;bpos += size;size = 0;// 该节点不够读的} else {// 有多少就读多少memcpy((char*)buf + bpos, m_cur->ptr + npos, ncap);m_position += ncap;bpos += ncap;size -= ncap;m_cur = m_cur->next;ncap = m_cur->size;npos = 0;}}
}

不改变m_position

void ByteArray::read(void* buf, size_t size, size_t position) const{if(size > getReadSize()) {throw std::out_of_range("not enough len");}size_t npos = position % m_baseSize;size_t ncap = m_cur->size - npos;size_t bpos = 0;Node* cur = m_cur;while(size > 0) {if(ncap >= size) {memcpy((char*)buf + bpos, cur->ptr + npos, size);if(cur->size == (npos + size)) {cur = cur->next;}position += size;bpos += size;size = 0;} else {memcpy((char*)buf + bpos, cur->ptr + npos, ncap);position += ncap;bpos += ncap;size -= ncap;cur = cur->next;ncap = cur->size;npos = 0;}}
}
setPosition
void ByteArray::setPosition(size_t v) {// 如果超出总大小,抛出异常if(v > m_size) {throw std::out_of_range("set_position out of range");}// 更新当前操作位置m_position = v;// 链表遍历到当前位置m_cur = m_root;while(v >= m_cur->size) {v -= m_cur->size;m_cur = m_cur->next;}// 如果在循环中找到了指定位置 v 的节点,并且指定位置 v 恰好等于当前节点的大小,// 则表示指定位置 v 在当前节点的末尾,需要将当前节点指针移动到下一个节点。if(v == m_cur->size) {m_cur = m_cur->next;}
}
addCapacity
void ByteArray::addCapacity(size_t size) {if(size == 0) {return;}// 剩余容量size_t old_cap = getCapacity();// 如果剩余容量足够则不需要增加if(old_cap >= size) {return;}// 需要扩充的数据大小size = size - old_cap;// 根据数据大小求得需要增加的节点大小size_t count = (size / m_baseSize) + (((size % m_baseSize) > old_cap) ? 1 : 0);// 遍历链表到末尾Node* tmp = m_root;while(tmp->next) {tmp = tmp->next;}// 第一个扩展的节点Node* first = NULL;for(size_t i = 0; i < count; ++ i) {tmp->next = new Node(m_baseSize);if(first == NULL) {first = tmp->next;}// tmp一直指向末尾tmp = tmp->next;// 扩大容量m_capacity += m_baseSize;}// 若剩余容量为0,则跳到下一个节点,才扩展的,什么数据都没有if(old_cap == 0) {m_cur = first;}
}

测试

代码

#include "../sylar/bytearray.h"
#include "../sylar/sylar.h"static sylar::Logger::ptr g_logger = SYLAR_LOG_ROOT();void test() {
#define XX(type, len, write_fun, read_fun, base_len) {\std::vector<type> vec; \for(int i = 0; i < len; ++i) { \vec.push_back(rand()); \} \sylar::ByteArray::ptr ba(new sylar::ByteArray(base_len)); \for(auto& i : vec) { \ba->write_fun(i); \} \ba->setPosition(0); \for(size_t i = 0; i < vec.size(); ++i) { \type v = ba->read_fun(); \SYLAR_ASSERT(v == vec[i]); \} \SYLAR_ASSERT(ba->getReadSize() == 0); \SYLAR_LOG_INFO(g_logger) << #write_fun "/" #read_fun \" (" #type " ) len=" << len \<< " base_len=" << base_len \<< " size = " << ba->getSize(); \
}XX(int8_t, 10, writeFint8, readFint8, 1);XX(uint8_t, 10, writeFuint8, readFuint8, 1);XX(int16_t,  10, writeFint16,  readFint16, 1);XX(uint16_t, 10, writeFuint16, readFuint16, 1);XX(int32_t,  10, writeFint32,  readFint32, 1);XX(uint32_t, 10, writeFuint32, readFuint32, 1);XX(int64_t,  10, writeFint64,  readFint64, 1);XX(uint64_t, 10, writeFuint64, readFuint64, 1);XX(int32_t,  10, writeInt32,  readInt32, 1);XX(uint32_t, 10, writeUint32, readUint32, 1);XX(int64_t,  10, writeInt64,  readInt64, 1);XX(uint64_t, 10, writeUint64, readUint64, 1);
#undef XX#define XX(type, len, write_fun, read_fun, base_len) {\std::vector<type> vec; \for(int i = 0; i < len; ++i) { \vec.push_back(rand()); \} \sylar::ByteArray::ptr ba(new sylar::ByteArray(base_len)); \for(auto& i : vec) { \ba->write_fun(i); \} \ba->setPosition(0); \for(size_t i = 0; i < vec.size(); ++i) { \type v = ba->read_fun(); \SYLAR_ASSERT(v == vec[i]); \} \SYLAR_ASSERT(ba->getReadSize() == 0); \SYLAR_LOG_INFO(g_logger) << #write_fun "/" #read_fun \" (" #type " ) len=" << len \<< " base_len=" << base_len \<< " size=" << ba->getSize(); \ba->setPosition(0); \SYLAR_ASSERT(ba->writeToFile("/tmp/" #type "_" #len "-" #read_fun ".dat")); \sylar::ByteArray::ptr ba2(new sylar::ByteArray(base_len * 2)); \SYLAR_ASSERT(ba2->readFromFile("/tmp/" #type "_" #len "-" #read_fun ".dat")); \ba2->setPosition(0); \SYLAR_ASSERT(ba->toString() == ba2->toString()); \SYLAR_ASSERT(ba->getPosition() == 0); \SYLAR_ASSERT(ba2->getPosition() == 0); \
}XX(int8_t,  10, writeFint8, readFint8, 1);XX(uint8_t, 10, writeFuint8, readFuint8, 1);XX(int16_t,  10, writeFint16,  readFint16, 1);XX(uint16_t, 10, writeFuint16, readFuint16, 1);XX(int32_t,  10, writeFint32,  readFint32, 1);XX(uint32_t, 10, writeFuint32, readFuint32, 1);XX(int64_t,  10, writeFint64,  readFint64, 1);XX(uint64_t, 10, writeFuint64, readFuint64, 1);XX(int32_t,  10, writeInt32,  readInt32, 1);XX(uint32_t, 10, writeUint32, readUint32, 1);XX(int64_t,  10, writeInt64,  readInt64, 1);XX(uint64_t, 10, writeUint64, readUint64, 1);#undef XX
}int main(int argc, char** argv) {test();return 0;
}

结果

image-20240306195337649

image-20240306195609579

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/724361.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

某酷ckey140逆向(之前下架了重新上传补发)

声明: 本文章中所有内容仅供学习交流使用&#xff0c;不用于其他任何目的&#xff0c;不提供完整代码&#xff0c;抓包内容、敏感网址、数据接口等均已做脱敏处理&#xff0c;严禁用于商业用途和非法用途&#xff0c;否则由此产生的一切后果均与作者无关&#xff01;wx a15018…

Python - Pycharm 配置 autopep8 并设置快捷键

什么是 PEP8 官方&#xff1a;PEP 8 – Style Guide for Python Code | peps.python.org PEP8 是 Python 官方推出的一套编码的规范&#xff0c;只要代码不符合它的规范&#xff0c;就会有相应的提示&#xff0c;还可以让代码自动的格式化 Pycharm 自带的代码格式化 ​ 但这…

2024年Android笔试题总,如何系统全面性学习Android语言

开头 让我们一起来看看&#xff0c;字节跳动的第三面&#xff0c;面试官都问了什么&#xff1f;&#xff08;第一二面的题目及答案已整理&#xff0c;需要的可以在文末领取&#xff09; 从七月中旬开始&#xff0c;我前前后后差不多一共投递了八十份简历&#xff0c;到目前为…

章鱼网络进展月报 | 2024.2.1-2.29

章鱼网络大事摘要 1、Omnity 完成了核心组件的原型开发&#xff0c;正在测试&#xff0c;未来将首先支持 Runes 资产跨链。 2、$NEAR Restaking 质押总量超过400万美元。 3、章鱼网络受邀参加 ETHDenver 2024&#xff0c;并且与 ICP 共同组织活动&#xff0c;介绍 Omnity 的…

【C语言】linux内核netif_receive_skb

一、中文注释 /*** netif_receive_skb - 从网络处理接收缓冲区* skb: 要处理的缓冲区** netif_receive_skb() 是主要的数据接收处理函数。* 它总是成功的。由于拥塞控制或协议层的原因&#xff0c;缓冲区可能在处理过程中被丢弃。** 这个函数只能在软中断&#xff08;softirq&…

guava的使用

对数组操作前判断是否会越界&#xff1a; List<String> s new ArrayList<>();System.out.println(Preconditions.checkElementIndex(2,s.size(),"下标长度超过了")); 是否为空 String s null;System.out.println(Preconditions.checkNotNull(s)); 判空…

Android使用Sensor.TYPE_STEP_COUNTER计步器传感器进行步数统计

1、首先&#xff0c;申请权限 必须声明 ACTIVITY_RECOGNITION 权限&#xff0c;以便您的应用在运行 Android 10 (API 级别 29) 或更高版本的设备上使用此传感器。 Manifest.xml也记得声明 if (Build.VERSION.SDK_INT > Build.VERSION_CODES.P) {Log.d(TAG, "[权限]&quo…

Docker的安装跟基础使用一篇文章包会

目录 国内源安装新版本 1、清理环境 2、配置docker yum源 3、安装启动 4、启动Docker服务 5、修改docker数据存放位置 6、配置加速器 现在我们已经完成了docker的安装和初始配置。以下为基本测试使用 自带源安装的版本太低 docker官方源安装的话速度太慢了 所以本篇文…

iOS 自动化测试踩坑(一): 技术方案、环境配置与落地实践

移动端的自动化测试&#xff0c;最常见的是 Android 自动化测试&#xff0c;我个人觉得 Android 的测试优先级会更高&#xff0c;也更开放&#xff0c;更容易测试&#xff1b;而 iOS 相较于 Android 要安全稳定的多&#xff0c;但也是一个必须测试的方向&#xff0c;这个系列文…

STM32(18)I2C

串口通信缺点 一个设备就需要一个串口&#xff0c;单片机可能没有那么多串口外设 总线/非总线 主机&#xff1a;负责管理总线&#xff0c;可控制波特率、数据的通信方向 波特率&#xff1a;由主机产生波特率信号 数据的传输 每个从机都有7位地址&#xff0c;最后移位是读&a…

力扣--动态规划516.最长回文子序列

思路分析&#xff1a; 创建一个二维动态规划表dp&#xff0c;其中dp[i][j]表示在子串s[i...j]中的最长回文子序列的长度。初始化基本情况&#xff1a;对角线上的元素dp[i][i]都为1&#xff0c;因为单个字符本身就是长度为1的回文子序列。从字符串末尾向前遍历&#xff0c;填充…

IPsec VPN协议框架

IPsec是IETF&#xff08;Internet Engineering Task Force&#xff09;制定的一组开放的网络安全协议。它并不是一个单独的协议&#xff0c;而是一系列为IP网络提供安全性的协议和服务的集合&#xff0c;包括认证头AH&#xff08;Authentication Header&#xff09;和封装安全载…

第十三届蓝桥杯大赛软件赛省赛C/C++ 大学 B 组 统计子矩阵

#include<iostream> #include<algorithm> #include<cstring> #include<string> #include<vector> #include<queue>using namespace std;int cnt,temp; int n,m,K; int a[505][505]; int pre[505][505];//二维前缀和void sol() {cin>>…

如何从产品的角度做好内容营销?媒介盒子支招

内容运营就是指将生产传播内容并进行重组&#xff0c;去满足用户的内容消费需求&#xff0c;想要提高内容运营的效果&#xff0c;媒介盒子认为可以从产品出发&#xff0c;将内容运营与品牌产品相结合。那么应该怎么做呢&#xff1f;接下来就让媒介盒子告诉你。 一、 场景化内容…

MySQL 空间碎片详解

文章目录 前言1. 空间碎片如何产生2. 空间碎片如何查看3. 空间碎片如何回收后记 前言 MySQL 数据库在运行过程中&#xff0c;随着时间的推移&#xff0c;可能会出现空间碎片的问题。空间碎片是指数据库表中不再使用的空间&#xff0c;但由于各种原因&#xff0c;这些空间并没有…

【漏洞复现】CVE-2023-27178 GDidees CMS任意文件上传漏洞复现

漏洞描述 漏洞编号&#xff1a;CVE-2023-27178 GDidees CMS是法国一款开源的网站管理工具&#xff0c;可用于创建站点、照片或视频库。GDidees CMS 3.9.1及以下版本存在任意文件上传漏洞&#xff0c;允许未经授权的攻击者上传精心构造的文件并执行任意代码。 影响版本 GDide…

国内企业怎么创建WhatsApp Business账号?

什么是WhatsApp Business 账号&#xff1f; WhatsApp是世界上最受欢迎的消息应用程序之一&#xff0c;拥有超过25亿月活用户。近年来&#xff0c;WhatsApp 推出了一项新产品——WhatsApp Business 。 那么&#xff0c;WhatsApp Business 究竟是什么呢&#xff1f;WhatsApp Bu…

【EI会议征稿通知】第四届人工智能,大数据与算法国际学术会议 (CAIBDA 2024)

第四届人工智能&#xff0c;大数据与算法国际学术会议 (CAIBDA 2024) 2024 4th International Conference on Artificial Intelligence, Big Data and Algorithms 由河南省科学院、河南大学主办&#xff0c;河南省科学院智慧创制研究所、河南大学学术发展部、河南大学人工智能…

Android工作资料,这份火爆全网的452页Android Framework内核解析

为什么想跳槽&#xff1f; 简单说一下当时的状况&#xff0c;我在这家公司做了两年多&#xff0c;这两年多完成了一个大项目&#xff0c;作为开发的核心主力&#xff0c;开发压力很大&#xff0c;特别是项目上线前的几个月是非常辛苦&#xff0c;几乎每晚都要加班到12点以后&a…

前方高能,又一波Smartbi签约喜报来袭

近期&#xff0c;交通银行、厦门国际银行、中原农业保险、江苏中天科技等多家知名企业签约Smartbi&#xff0c;携手Smartbi实现数据驱动业务新增长。 Smartbi数10年专注于商业智能BI与大数据分析软件与服务&#xff0c;为各行各业提供提供一站式商业智能平台&#xff08;PaaS&a…