if(){ }else if() { }else if() { }else if() { }else if() { }else if() { }else if() { }else if() { }else if() { }else if() { }else if() { }else if() { }else if() { }else if() { }else if() { }else if() { }else if() { }else if() { }else if() { }else if() { }else if() { }else if() { }else if() { }else if() { }else if() { }else if() { }else if() { }else if() { }else if() { }else if() { }else if() { }else if() { }else if() { }else if() { }else if() { }else if() { }else if() { }else if() { }else if() { }else if() { }else if() { }else if() { }else if() { }else if() { }else if() { }else if() { }else if() { }else if() { }else if() { }else if() { }else if() { }else if() { }else if() { }else if() { }else if() { }else if() { }else if() { }else if() { }else if() { }else if() { }else if() { }else if() { }else if() { }else if() { }else if() { }else if() { }else if() { }else if() { }else if() { }else if() { }else if() { }else if() { }else if() { }else if() { }else if() { }else if() { }else if() { }else if() { }else if() { }else if() { }else if() { }else if() { }else if() { }else if() { }else if() { }else if() { }else if() { }else if() { }else if() { }else if() { }else if() { }else if() { } else { }
感受一下几百行if-else
找工作的小伙伴儿可以看一下Google的算法面试题,了解一下算法的重要性。
SpaceX用到C/C++、JavaScript、Python等。
以SpaceX的龙飞船软件系统为例说明主流编程语言的区别。
学习编程有哪些坑?怎么避开?
校招是转型的绝佳机会,如何利用校招实现转型C++软件。
自学编程过程中看书和coding需要形成闭环。
读大学或研究生甚至博士目标只有一个,就是找个好工作,那就不用太纠结专业,专业仅仅是一次选择而已,看职场需要什么能力主动去锻炼就可以了。
机械大一的小伙伴儿迷茫了怎么办呢?
了解大学期间除了专业课还需要学什么?
机械专业需要注重跨学科知识的学习,早规划等毕业的时候选择更多。
从车企的招聘可以看出传统机械已经不能适应时代的发展,需要注重跨学科知识的学习,比如加强编程和算法的学习。
俺自己写的盲听铜铁的代码, 应该是很烂的。
希望抢到一个最烂的位子。
给耳机音响发烧友一键生成盲听铜铁抓阄纸条的 JavaScript 网页脚本代码,不用安装任何软件
效果
Click the button to display a random testing order Try it 铜___1 铜___2 铜___3 铁__1 铜___4 铁__2 铁__3 铜___5 铁__4 铜___6 铁__5 铜___7 铁__6 铁__7 铁__8 铜___8 铜___9 铁__9 铁__10 铜___10
代码如下:
这是生成 20 次盲听的代码。
<!DOCTYPE html> <html> <body> <p>Click the button to display a random testing order</p> <p> 给耳机音响发烧友一键生成盲听铜铁抓阄纸条,不用安装任何软件 点击生成</p> <button onclick = "myRandomSTR()">Try it</button> <br> <br> <p id="Result_Demo"></p> <br> <br> <script> function myRandomSTR() { var ls_tmp =""; var li_i, li_j; var li_tmp = 0; var ld_myRand = 0; var li_a, li_b; do { ls_tmp = ""; li_a = 1; li_b = 1; for (li_i = 0; li_i < 20; li_i++) { for (li_j = 0; li_j < 50; li_j++ ) { li_tmp = ( Math.random() - 0.5 ) * 68719476736; ld_myRand = li_tmp / 68719476736; } ls_tmp += (ld_myRand >0) ? ("铁__" + String (li_a++) + "<br>" ) : ( "铜___" + String (li_b ++) + "<br>" ); } } while (li_a != li_b); document.getElementById("Result_Demo").innerHTML = ls_tmp; //ls_tmp = null; } </script>
大概就是,一个if循环19.8亿次,而且7年没人敢动....
真事,就出现在知名游戏大厂R星的知名大作 GTA 5 中。
而且,19.8亿次的if循环,今天仍然在世界各地的玩家cpu上跑着。
————————————————————————————————————————————
3月16日更新:
GTA 5“屎山”代码后续来了。
R星终于官宣准备修复了!
主动改善玩家游戏体验?不存在的。
要主动,哪里还要等七年?
这篇揭R星老底的帖子在全网大火后,R星不得不出面应对。
在和黑客大哥联系后,R星认可了他的改进方法,宣布在后续更新中修复相关问题,并且还慷慨的给他付了一笔1万美元的奖金。
鉴于R星失误实在太低级太离谱,而这位老哥的方法又太有效,以致无数玩家称他“功德无量”。
如果平均给每个玩家节省10秒,全球500万玩家一天就能节约5000万秒,一年中,节约的总时间大概能有数十年。相当于挽救了十多个人的生命!
“事了拂衣去,不留功与名”,这位黑客大哥被无数玩家膜拜。当然大家也不忘再把R星拖出来“鞭尸”。
有人吐槽,GTA 5仅2020年就买了2000万份,累计销量更是达到1.4亿份,R星每年都能从这个项目上赚数亿美元,但是却不肯花几分钟去解决这么一个低级错误,实在可耻。
还有人抨击R星几乎从不与玩家社区互动,玩家提的意见也从来充耳不闻,直到这次被被黑客嘲讽打脸,才不得不出来表态。
事后,黑客大哥还透露了一丝身份信息,原来他人在拉脱维亚。
拉脱维亚是波罗的海国家,原来是前苏联加盟国之一。在网上搜索相关信息,可以发现“拉脱维亚黑客”,几乎是和俄罗斯黑客一样传奇神秘的存在。
有网友爆料,在拉脱维亚,普通程序员工资平均3-4k欧元(23000-31000人民币)。
而他领到的这1万美元奖金,相当于三四个月工资了。
提前领了一笔“年终奖”,黑客大哥表示很开心。同时他也说,将密切关注GTA 5未来更新,一丝不苟的检查修复情况。
不知道他还能不能从R星领走更多奖金
(注:所谓“屎山”,是程序员间流传的一个梗,指陈年累月且复杂低效的代码,因为改动成本巨大,所有人避之不及。)
——————————————————————————————————————————
GTA 5“屎山代码”前情回顾:
一支烟的功夫,GTA 5联机版终于打开了。
「7年了!GTA 5联机版加载还是这么慢??」
△Please wait forever to play
Reddit、Steam、HackerNews上,无数玩家吐槽抱怨……
进游戏少则等5、6分钟,多则20分钟。
终于,一个黑客大哥实在忍不了,用逆编译器逐条查看运行情况,终于找到原因。
原来,R星(游戏开发商RockStar)写的代码太低效,加载时,一个if语句竟然循环了19.8亿次….
幕后黑手:谁占用大量时间?
加载GTA 5 Online到底有多慢?
△硬件拉满的土豪玩家请无视
Reddit相关板块发起的调查中,超过80%的玩家,都要等3分钟以上,有的甚至超过15分钟。
而且,从7年前Online上线到今天,这个情况丝毫没有改善。
暴躁的,已经骂起了脏话……
但奇怪的是,如果你选择是故事模式(单机版),加载就会快很多,感觉甚至像两个不同的工作室开发的游戏。
具体到这位黑客大哥的例子,他自己的硬件配置如下:
CPU,是老而弥坚的AMD FX-8350,2012年上市,采用“推土机”架构,超频潜力惊人。
显卡还是GTX 1070。
这样今天看起来老旧的配置,打开单机版GTA 5需要1分10秒,而加载联机版则6分钟起。
黑客大哥用了最简单的Windows任务管理器,来判断联机版GTA 5在启动时,都调用了哪些计算机资源。
在1分钟的时间分界线上,之前是加载的是单机和联机版通用的基础内容,之后是联机版独有的内容。
可以看到,联机版GTA 5,加载时调用大量CPU资源至少长达4分钟之久。
而同时,内存、GPU、硬盘的使用情况几乎没有明显变化。
所以,问题大概率出在代码上。
“R星代码写太烂!”
黑客大哥在开扒R星代码之前,就说:
我闻到一股烂代码的味道…..
为了找出到底那一部分程序卡住了CPU,他使用了工具Luke Stackwalker,对CPU任务堆栈进行采样分析。
Luke Stackwalker对于闭源应用程序,可以转存正在运行的进程堆栈,和当前指令指针的位置,以一定时间间隔建立一个调用树。
最后将数据整合,就可以得到程序运行统计数据。
从结果上看,一共有两个函数“卡住”了CPU:
于是他使用专业的代码拆解工具,给GTA 5来了一个“开膛破肚”。
沿着调用栈往下走,发现问题出在一个sscanf函数上。
sscanf的功能是读取格式化的字符串中的数据,而在GTA 5中,它正在读取的是一个10M左右,有63000多个条目的JSON文件。
这个文件到底是干什么用的?黑客大哥推测,这可能是游戏内购商店的相关内容。
在具体运行时,sscanf对于每个有效值,逐个读取每一个字符,然后返回结果,之后指针移向下一个值,循环往复……直到把10M文件全部扫一遍。
再看第二个问题,这是一个存储命令,对象是item,具体是什么不得而知。
但是保存前,有一个if语句,逐一比较item内项目的哈希值,检查它们是否出现在某一列表中。
按照他的计算,这一步if,要执行(63000^2+63000)/2 = 1984531500次!
没错,等待加载前的十多分钟里,GTA 5用你的CPU,执行了19.8亿次if命令。
如此简单粗暴的编程思路,让这位老哥哭笑不得:
既然对象有唯一哈希值,那为什么不用hash map???
(hashmap根据hashCode值存储数据,大多数情况下可以直接定位到它的值,因而具有很快的访问速度,但遍历顺序不确定。)
至于为什么这样,有网友推测最开始,if的循环次数并没有这么多,而是随着开发,条目不断增多,最后到了积重难返的地步。
而之前的代码结构,谁也不愿意去动。
就这样,19.8亿次if,一遍遍在世界各地玩家cpu上上演。。。
这是不是堪称游戏开发史上最意外的“屎山”代码?
问题解决,加载时间节省70%
至于第一个问题,黑客大哥采用hook大法,不一一读取字符串,而是:
hook strlen
“缓存 “字符串起始和当前长度。
如果在字符串范围内函数在此被调用,返回缓存的值
至于if语句问题,就更直接了——完全跳过重复检查,利用hash map插入项目,因为这些值是唯一的。
最后的结果如下:
现在,GTA 5联机版加载,从原来的6分钟,下降到现在的1分50秒!而且,用的还是七八年前的硬件配置。
在此,应该手动@R星:你学废了吗?
这位黑客大哥在博文中没有留下任何身份信息,也没有透露用的反编译工具,但是做好事不留名的他,把打好包的工具上传到了Github,玩家通过一行代码就能下载:
git clone —recurse-submodules https://github.com/tostercx/GTAO_Booster_PoC
之后,把dll文件粘贴到游戏根目录下就OK!
博客原文
https://nee.lv/2021/02/28/How-I-cut-GTA-Online-loading-times-by-70/
Github地址:
https://github.com/tostercx/GTAO_Booster_PoC
说个在公司看到的,承包了我一周笑点的一段代码。
事先说明,是C#代码,这段代码可读性还是很强的,而且能完成需求没有明显bug。要不是因为测试发现某处GC不正常,我也不会调查到这里。
这部分代码会定时调用,用于更新一堆类似活动的数据,数据定义为一个只有成员变量的struct然后放在一个list里。这段代码就是遍历这个list,然后调用另一个函数去更新相关的数据。循环遍历没啥好说的,关键是循环里面是这么写的:
ActiveData data = activeList[i]; data.start_time = UpdateActiveData(data).start_time; data.end_time = UpdateActiveData(data).end_time; data.xxx = UpdateActiveData(data).xxx; ...
其中ActiveData是定义数据的struct,UpdateActiveData是更新数据的函数,正如你所想的那样,传入的参数是ActiveData,然后经过一番繁琐的计算返回计算好的ActiveData。当然命名不是这样的,主要是我不记得原文了,而且命名很正常,不值得吐槽。
主要问题是,明明可以用ref传引用过去直接修改好了事的,就算不知道不会用ref就算了,直接用返回值重新给data赋值也是可以的,但是偏偏单独把这三个需要修改的成员变量拿出来分三次赋值。这个函数就完全没必要的执行了三次。
而且仔细研究这个函数之后发现,里面的问题也不少。因为逻辑有点复杂,计算比较繁琐。我还记着的是,有用类似的方式(指为了修改成员变量反复调用)调用另一个函数,还有为了在一个list里找到匹配的数据所以用循环遍历,但是找到了不break等等。
所以我改掉了之后,GC降低了三位数的样子。
题外话,这处代码虽然效率非常低,但是没bug,至少没有明显的bug。
而我照着原来的逻辑优化了一下之后,反而不小心把开始时间和结束时间搞反了。当时还没发现这个bug,反而是之后才发现的。
补充一下详细描述吧,看了大家的评论发现因为我没说清楚有的人误会我了。
1.这个项目是一个基于unity用c#写的游戏,所以对性能有一定要求。
2.是其他人发现了这个界面性能表现不好然后指定我来优化,profile告诉我这里因为这个写法消耗太大,所以我别无选择。
3.正如评论里大佬说的那样,ref不是必须甚至有点坏处的,我的笑点在于为了修改一个结构体的数据反复三次调用蠢萌蠢萌的。
4.我忘了你们看不到被重复调用的函数的内容了,里面的计算非常复杂,这个函数属于一个数据管理类,除了用到传入的结构体之外,还会用到各种各样的数据来完成各种判断和计算,简单的说就是消耗很大。
5.这部分的代码不是调用一次就ok了,而是定时调用,频率还很高,所以造成的消耗很严重。
翻硬盘时翻出来个小玩意,试了试 现在还能跑起来,哈哈。
当时答主事带一学生,很有精神;刚学一点西加加;头发浓密。萌新的憧憬之下,用devcpp这个瞎眼ide 写了个控制台坦克大战。3k来行
用txt文件储存地图,玩家可自己设计地图,哈哈。当时还想弄地图编辑器的,最后还是咕咕咕了
相当搞笑的一点:前面有段代码是获取地图文件夹下所有文件名,存入字符数组。当时不知道字符串尾部要加结束符,导致后面使用该字符串读入文件时屡屡崩溃(没有结束符嘛)。
于是,宇宙大聪明答主写了这么一段处理代码,大概思路是将这个字符串复制一遍,检查复制后字符串的长度是否异常,如果异常则再复制一遍……(笑哭.jpg)
程序的稳定性相当差劲,截这些图的时候答主n次卡死或控制台弹窗报错了……
是的,当时连printf都不知道,打印字符用效率奇低的cout;而且一帧里逻辑处理和渲染混杂执行,导致画面容易闪烁甚至掉帧……
这是敌人AI的一部分,用n个套娃循环实现类似A*算法的寻路。AI寻找能射击到敌人的最近位置,还会根据自身血量等计算攻击欲望,躲避可能伤害到自己的炮弹,拾取有用的道具。整套算法效率特别特别低,每帧都要完整执行一次;容易导致掉帧。所以场上只能有一只精英怪使用完整的AI,其他小怪用随机函数xjb乱逛。
类似移动、碰撞检测之类的部分,每一份代码都要手动复制四次,对应上下左右四个方向。
再放开头定义的一段吧:
#include <iostream> #include <windows.h> #include <fstream> //#include <iomanip> //#include <string.h> //#include <stdio.h> //#include <conio.h> #include <sstream> #include <cstdlib> #include <ctime> #include <io.h>//文件 #include <vector>//文件 #include<Mmsystem.h>//音频相关 #pragma comment(lib,"winmm.lib")//音频 #define KEY_DOWN(VK_NONAME) ((GetAsyncKeyState(VK_NONAME) & 0x8000) ? 1:0) using namespace std; int map[38][55] = {0};//储存地图 int mapX = 55;//地图x大小 int mapY = 38;//地图y大小 int allX = 30;//整体x位移 int allY = 0;//整体y位移 int circlet = 0;//循环次数 int model;//当前游玩模式 int buff[3] = {0};//0[储存道具:1动能弹,2高爆弹,9生命值 ] ,1x,2y int tank[10][8] = {0};//储存坦克:[编号],[0朝向,1x,2y,3生命值,4武器,5移速,6外形,7开炮装填时间] //武器:0普通炮,1速射炮,2高爆弹,3破甲弹,4跟踪弹 int shell[10][10][4] = {0};//储存炮弹:[所属坦克],[编号],[0朝向,1x,2y,3类型] int smoke[100][3] = {0};//储存硝烟"#":[编号],[0已歼灭时间,1x,2y] //**********基础功能********** void ProgramBegin();//初始化程序 void FuZhi();//初始赋值 void gotoxy(int,int);//跳转光标 void color(int);//颜色 void getFiles(string, vector<string>&);//获取目标文件夹内所有文件名 void InMap(char*);//将地图导入数组 void Tout(int);//打印地图方块 void PrintMap();//打印地图 void PrintTank(int,int,int,int);//打印坦克,朝向,x,y,类型 void DelTank(int,int);//屏幕删除坦克 void sound(int);//音效 //**********菜单界面********** int MenuModel();//模式菜单 char* MenuMap();//选图菜单 void MenuTank1vN();//选车菜单1vN模式 void MenuTank1v1();//选车菜单1v1模式 void Interface();//玩家状态界面 void NewUI();//刷新状态界面 int MenuPveWin(int);//结束人机战局 int MenuPvpWin(int);//结束双人战局 //**********战斗功能********** void CtrlTank(int,int);//坦克控制 void InShell(int);//载入炮弹 void SkyShell();//炮弹移动 bool HitTank(int,int,int);//判断坦克是否受到伤害 void HitShell(int,int,int,int,int,int);//炮弹命中.朝向,x,y,弹药类型,被命中者 ,发射者 void Boom(int,int,int);//爆炸。半径,x,y void InBuff();//载入道具 void OffBuff(int);//使用(消除)道具 void InSmoke(int,int);//载入硝烟 void OffSmoke();//删除硝烟 //**********AI功能********** int FindMap(int,int,int,int);//寻路。起点x,y,终点x,y。返回方向,1234上下左右。 void AiTank();//坦克AI void XieTank(int);//屑AI void Born(int,int);//生成新车,编号、难度 //**********模式********** void annihilate();//歼灭模式 void pve();//人机 void pvp();//双人对战 //**********附加********** void op();//片头 void loading();//伪加载界面 void xy();//显示硝烟参数 void pd();//显示炮弹参数 void MenuZhanYi();//战役
是不是已经想吐了~3.5k行代码塞在一个.cpp里
有没有留意到,上面那一堆全局变量的数据结构只有int数组。怎么用它储存数量和参数不等的坦克、炮弹、粒子(爆炸火光、烟雾……)等东西的呢?答主“发明”了这样一种方法:
一个数组储存一种类的物体,第一维记录该类所有物体。如坦克tank数组第一维长度是10,那地图上最多只能同时存在10辆坦克;tank[5] 表示编号为5的坦克。数组第二维储存这个物体的个体属性。如tank[5][0]代表5号坦克的朝向,[1]、[2]代表其坐标,等。
游戏中每一帧,依次遍历坦克、炮弹、特效等数组,提取数组中每一位物体,根据玩家输入和游戏逻辑更新该物体的参数(也就是更新这个数组)。
关键的来了:怎么实现生成和销毁物体,比如炮弹命中时销毁自身?很简单,要销毁物体,将那个物体所在的数组第二维清零就行了。刷新物体遍历数组时,遇到这种“空槽”就跳过。要增加物体,也遍历一遍该数组,找到一个“空槽”,将新物体的初始数据写入。
一段时间后,答主知道了有种东西叫结构体,有门课叫数据结构,有种数据结构叫链表,有种对象叫面向对象。可惜到现在也没找到对象,唉。
最烂的代码果然还是自己写的代码,这简直是个珠穆奥力给峰。好在没有人需要为这个“项目”负责,没有人需要维护它,它只需要静静地躺在硬盘深处的角落就行了。
总结:
后来呢我还是一直热衷于游戏开发。坦克大战后不久,写的第二个“大工程”是基于qt的2d海战游戏,有点类似顶视角的wows。为啥当时突然想学qt?也许是前一个学期写作业用mfc太痛苦了吧。总之,按我的坏习惯,看了两三天文档就兴冲冲开工写demo了。后期的代码和工程现在已经找不到了,只找到一些前期的素材:
代码风格也是极其奥力给,回忆一下大概像这样:
thisGuanQia->ui->myship->labelPao->setText("主炮0开火cd:"+QString::number(thisGuanQia->jianDui->ship->my->ship[0]->wuQiZu[0]->pao[0]->cd->nowCD));//在标签上打印 我方舰队 第一只船 第一个武器组 第一门炮的cd thisGuanQia->ui->myship->labelKey->setText("W"+QString::number(ctrl.keyW)+"S"+QString::number(ctrl.keyS)+"A"+QString::number(ctrl.keyA)+"D"+QString::number(ctrl.keyD)+"↑"+QString::number(ctrl.keyUp)+"↓"+QString::number(ctrl.keyDown)+"←"+QString::number(ctrl.keyLeft)+"→"+QString::number(ctrl.keyRight)+"GN"+QString::number(ctrl.keyShift)+QString::number(ctrl.keyCtrl)+QString::number(ctrl.keyAlt)); //打印键盘事件
那时不会调编辑器缩进,现在也忘了qt原生编辑器是长啥样的。反正基本上整段代码写在一行里…………看代码要按紧shift+滚轮,对小拇指可是个考验。
终于会用队列了,比如开炮时的火光就是将特效序列帧存入一个队列,然后每帧读取一张。还写了一套几何碰撞检测,虽然只支持点、线、椭圆、长方形这四种元素。逻辑和渲染终于分开了。还写了一段帧率控制算法,按本帧逻辑部分的执行时间决定接下来sleep多久,使帧率尽量稳定在30。(然而加这玩意使帧率更不稳定,后来全删了)
最后先帝创业未半而中道崩黜,加了很多功能后帧率实在太低。用的应该是2代i5m集显,帧率只有12不到……
再后来接触了“真正”的游戏引擎,发现啊这个也太方便太易用了。原来之前花了很多时间瞎折腾的烂代码,在引擎里竟然全有对应功能的接口?我想到的,前人全都想到过啊。
随便聊一下,引擎里最让我愉悦的是cocos creator。新手教程级的文档,极其舒服的环境。对比隔壁unity,去unity和ccc的api文档用中文搜点东西,ccc大多是你想要的,unity文档搜索是什么玩意。ccc的中文社区建设的也挺好,活跃度高,干货多;而且中文搜索引擎能直接搜到帖子内容。unity中文社区,??? 可惜ccc本质还是个轻(棋)量(牌)级引擎,论功能跟unity、ue暂时还没得比。
viod mian()
{
print(“hello word”)
}
当我同学指着如上代码,问我为什么报错的时候,只有一张图片可以说明我的心情:
本站所有内容均为互联网搜索引擎提供的公开搜索信息,本站不存储任何数据与内容,任何内容与数据均与本站无关,如有需要请联系相关搜索引擎包括但不限于百度,google,bing,sogou 等
© 2025 tinynews.org All Rights Reserved. 百科问答小站 版权所有