一些快速提高C++程序性能的方法

记录一些快速提高C++程序性能的方法。因为运行环境存在差异,样例CPU耗时结果仅供参考。

运行机器CPU信息:

Architecture: x86_64
CPU op-mode(s): 32-bit, 64-bit
Address sizes: 46 bits physical, 48 bits virtual
Byte Order: Little Endian

CPU(s): 2

CPU MHz : 2500

Caches (sum of all):
L1d: 32 KiB (1 instance)
L1i: 32 KiB (1 instance)
L2: 1 MiB (1 instance)
L3: 33 MiB (1 instance)

编译与CPU耗时统计方式:

//gcc version 12.2.0
//g++ -std=c++11 -g -O2 -o test test.cpp
int main()
{
    clock_t start = clock();

    //测试函数

    double elapsedTime = static_cast<double>(clock()-start) / CLOCKS_PER_SEC;
    std::cout << elapsedTime << "s" << std::endl;
    return 0;
}

一、std::vector相关

1、大小已知场景使用reserve()

Read More →

一种基于动态库C++热更新的方式

c++程序的热更新一直是让c++开发者头疼的事情,一般场景下若线上业务逻辑需要修复,常规更新方式是重启进程,整体风险较高。

造成这种现象原因之一是c++比较“古老和灵活“。区别于脚本语言以及一些现代诞生出来就支持热更新的语言,它有其特有的适用场景与技术积累(褒义和贬义都有)。

写这篇文章的时候,c和c++两兄弟仍然可以位居编程语言流行榜前5:

tiobe-2023.10编程语言流行排行

一些成熟的c++热更新方式

这里热更新定义范围会放的宽一些,理解成对业务影响小的更新方式

虽然c++自身不具备热更新的能力,但是也有一些其他手段来达到此目标:

  1. 逻辑设计成无状态,平滑重启,现在随着容器与无服务推广,应用面更广了;
  2. 子进程替换,nginx在使用;
  3. 所有逻辑编入动态库,利用动态库热加载机制;
  4. 利用lua脚本语言处理逻辑,skynet在使用,很多游戏服务器在使用此方案;

这篇文章会介绍一种基于动态库的方式,和上面第3点主要区别在于对于原工程侵入较小,底层机制也有所不同。

Read More →

聊聊手游服务器从开发到上线

一篇关于手游服务器开发的经验沉淀,围绕项目生命周期顺序展开,比较泛的进行一些重点介绍与列举。

首先,通常来说手游的项目生命周期分为下面几个大步骤:

整个周期少则1~2年,多则3~5年,时长与项目的体量、预期、市场环境都有关系。

那么作为一个服务器开发除开项目常规需求外,还需要做哪些事情,或者要注意哪些事情呢?

一、立项

项目立项成功一般需要能体现核心玩法的demo支撑,这些demo通常是客户端单机,换句话来说无需服务器过多参与立项环节,这是一个好又不好的流程。

好的地方在于在少了服务器这一部分的交互,能极大的提升demo产出效率,快速出原型;

不好的地方在于:若在游戏整体设计上存在服务器难点而在立项的时候没有很好或者成熟解决方案,可能会是一个隐患,严重的话会直接影响项目研发整个周期,甚至会影响项目是否能够上线;

所以说这是一个好又不好的流程。

除了demo参与度的问题外,在立项的时候服务器还需关注哪些事情?

  • 确定开发语言、框架、存储:开发语言和框架一般是项目所在公司祖传的,一些小公司可能会由主程主导(其实也是另一种方式的祖传),所以这里不过多展开;
  • 初步确定分区分服还是全区全服:听游戏设计者的,这里只是初步确定,要留后路
  • 根据游戏类型确定核心机制:比如游戏核心玩法是回合制、MMO、MOBA或者SLG等。涉及到核心玩法采取状态同步还是帧同步,还是客户端计算服务器校验战报等等。(这里多说一句,这个环节也是上面方案服务器不参与的坑点所在:现在很多游戏挂着回合制或者MMO设计说明,但是实际设计上已经脱离了传统回合制或者MMO的范畴,设计人员大概率不懂这个,他们觉得稍微变一些玩法还是回合制或者MMO,且方案业界早已实现过的,但是实际开发方案早已大相径庭);
  • 人员梯队:需要及时和+1、+2对齐,整体HC数量需要结合项目体量和研发期结合来看,低中高的人员梯队尽量存在;
Read More →

图数据库在游戏开发中使用思考(下)

在游戏开发设计中,玩家作为一个游戏对象,他们之间可能会存在多种关系,比如:

  • 好友关系;
  • 组队关系;
  • 公会关系;
  • 买卖关系;
  • ……

无论是使用关系型数据库还是key/value型的NoSQL ,按照传统思路对于这些关系的存储结构实现大同小异。例如:玩家会有独立的好友关系数据块,数据块内部至少存储着好友唯一ID,可以通过好友ID索引到好友相关信息;公会存在唯一ID,通过这个ID拉取公会成员信息和其他信息;组队与公会类似…

传统数据库与图数据库思路比对

我们列举一下好友与公会的常规操作,在不考虑合法性校验的情况下:

操作传统方式图数据库方式
玩家A和B成为好友玩家A和B分别在好友数据块增加对方ID两对象A和B之间增加
玩家A和B解除好友 玩家A和B分别在好友数据块删除对方ID 两对象A和B之间删除
玩家A加入公会C玩家A记录所属公会ID,公会C在成员列表加入玩家A两对象A和C之间增加
玩家A离开公会C 玩家A删除所属公会ID,公会C在成员列表删除玩家A 两对象A和C之间删除

直观感觉上使用图数据库方式更简洁一些,传统方式若做得严谨一些可能还需要考虑事务的支持,根据数据库的选型不同会导致数据一先一后写入,需处理先写入成功后写入失败的场景。

为了凸显差异性,这次我们再假设一些更复杂的操作,比如在推荐系统中常见的场景,同样也是不考虑合法性校验的情况下:

Read More →

图数据库在游戏开发中使用思考(上)

图数据库,从概念的出现到现今蓬勃发展跨度几十年,它可以细分为NoSQL(Not Only SQL)非关系型数据库的一种:

图的概念

图数据库这里的“图(Graph)”并非指的风景、人物照片或者GIF动图,而是指类似于“网络拓扑图”或者“股权关系图”这类结构的图,目的用来表示节点之间的关系,这类结构图也可以称之为属性图模型

还有其他概念分类,例如:资源描述框架(Resource Description Framework,RDF)。这里我们不做延伸,本文后续图的概念均围绕属性图展开。

Read More →

基于QUIC的HTTP/3

大概几个月前,HTTP/3被标准化(RFC 9114),它最大改变是将HTTP传输协议从TCP变成了基于UDP的QUIC,这意味着HTTP不在是绝对的基于TCP的应用层协议。

新技术标准的成立必然有它的使命和原因,关于HTTP/3相关概念、原理的介绍在互联网上很多,本篇文章会减少这方面的介绍,更多聚焦于为什么会出现这种技术,我们为什么需要它的思考,从不一样的角度来看待这个新技术标准。

Read More →

排查Https请求超时的一些建议(下)

上篇内容我们介绍了排查Https请求超时的大体思路,主要强调了自查的重要性,以及一般情况下超时问题的排查流程与关注点。

下篇我们将内容聚焦到超时自查中一块十分重要的内容:在请求通过网卡发出之前,如何有效获取Https请求各阶段Trace记录。目的是为了更好的进行问题归因和为后续排查收集关键信息。

客户端发起Https请求的流程

我们还是以Golang语言作为背景,其标准库客户端请求流程如下:

对于上面每个步骤来说,我们既需要关注耗时,还需要关注它们是否存在错误,特别针对DNS解析我们还需额外关注DNS解析的结果

每个步骤的目地和原理这里不做介绍,比如:链接复用是什么;DNS解析是做什么用的,解析结果会影响什么等等。这里我们更加聚焦于怎么Trace每个步骤需要关注的信息。

Trace方案

Read More →

排查Https请求超时的一些建议(上)

超时问题

Https请求超时一般指的是从Https请求发起计时,在规定时间内没有返回响应,造成请求超时。使用Golang语言为例,默认客户端没有设置超时时间,即请求一直等待返回,我们可以通过下面代码设置客户端的请求超时为5秒:

var client = &http.Client{
    Timeout: time.Second * 5,
}

绝大多数情况下,超时请求会返回关键词为“context deadline exceeded (Client.Timeout exceeded while awaiting headers)”的错误信息,少数情况下也会因为超时导致的其他信息返回。

Golang语言也支持设置更细粒度的超时机制,例如在TCP链接建立阶段,在SSL握手阶段等等,主要是根据Https请求阶段来进行分解的。可以参考这边文章(The complete guide to Go net/http timeouts),文中讲解的比较透彻,借里面的一张图来说明更细粒度的请求超时拆解:

其中http.Client.Timeout就是文章开头代码所设置的超时时间,本文讨论的超时也是从这个点延伸出去的。其余的超时机制以及Trace它们方式会在下篇里做详细介绍。

问题发现

Read More →

聊聊服务端架构演化

我们先从一个需求入手:需要实现一个游戏好友系统。

比较常规的做法:

(为了更清晰讲述,这里屏蔽了一些模块细节:比如接入层、服务器框架、储存读写分离、分表、分桶、集群模式等,也未考虑异地互备与大陆外数据等细节)

简单的名词解释:

  • GameServer:用来接收、处理客户端常规请求的模块(根据游戏是否分区部署策略会有所不同,这里简化成单分区);
  • FriendServer:用来处理所有和好友相关内容的模块,可以多进程部署进行负载均衡;
  • RoleBaseInfo:用来储存用户简要信息;(这里可以简单理解成需要在好友列表上展示的信息)

那么整个处理流程如下:请求从GameServer转发至FriendServer,FriendServer根据好友列表信息拉取对应好友的RoleBaseInfo返回至客户端显示。

将来会引发的问题:

随着用户的增多,每次请求通过FriendServer去DB实时拉取系统整体负载会加大。

解决方案:所以为了成本考虑增加缓存,牺牲一定的实时性,空间换时间的策略。

新流程接入了缓存机制会把好友数据做缓存,分钟级定期更新(缓存也可以放GameServer,可以减少内网带宽消耗,但是造成模块边界模糊)。

Read More →

GameAISDK的UI识别点击

游戏对不同分辨率手机的适配没有统一的标准或者固定方案,这样会引发一些微妙的问题,我们先拿简单的分辨率缩放举例。

在传统的图像缩放中,对于目标点的偏移都是等比的。比如:从(100×200像素)->(200×400像素)图像中的UI坐标变化过程是这样:

矩形坐上角坐标原点,横向x轴,纵向y轴,后面均沿用此规则

传统图像缩放

对于游戏来说,因为大屏、异形屏的原因较难按照等比的方法来进行适配,而且部分游戏可能有自己独特的分辨率适配规则。实际情况可能如下:

游戏图像适配

这会导致一个什么样的问题?比如你有一段程序或者代码需要ClickUI(40,50)。在100×200原始分辨率下是有效的,但是换了200×400分辨率的手机可能会有适配问题,原本期望的ClickUI(80,100)无效了。实际需要ClickUI(80,120)才有效。

当然这里有一些其他的方式能取到不同分辨率下UI的精确坐标,游戏可以利用引擎,android原生app可以利用底层接口等等,但是这些不是本文讨论的内容。

GameAISDK点击UI的过程

Read More →