关于memory:UnreadableMesh内存占用翻倍问题

1)Unreadable-Mesh内存占用翻倍问题2)在TMP中计算书名号《》高度的问题3)Mipmap如何限定层级4)FMOD设置中对于Virtual Channel Count&Real Channel Count的参数疑难 这是第374篇UWA技术常识分享的推送,精选了UWA社区的热门话题,涵盖了UWA问答、社区帖子等技术知识点,助力大家更全面地把握和学习。 UWA社区主页:community.uwa4d.comUWA QQ群:465082844 MemoryQ:最近在做性能剖析时发现Mesh内存占用异样,局部Mesh在未开启Readable的状况下也不会卸载CPU局部的内存开销。 通过Mesh文件比照发现是m_KeepVertices与m_keepIndices参数差别导致: 查问到网络上的局部源码,发现该参数的确会影响内存开释。 已尝试通过批改文件的形式与SerializedObject的形式去批改这个值,都无奈保留,Unity外部会自行修改。请问有谁晓得它具体的实现细节,什么样的Mesh数据会导致它会保留CPU端所有数据,以及有何提前躲避的形式? A:可能是网格设置了动态合批导致的,能够试下关掉动态合批看下这部分内存是否被卸载。感激宗卉轩@UWA问答社区提供了答复 UIQ:如下图所示,输出的文字中有书名号,但应用ContentSIzeFitter计算失去的高度是谬误的(貌似它计算的比理论少,导致没换行): 而应用默认的Text就没这个问题(雷同的字体): 请问如何在TMP中计算书名号《》的高度? 针对以上问题,有教训的敌人欢送转至社区交换分享:https://answer.uwa4d.com/question/65c1d43b40a8d93b624afcce AssetQ:请问Mipmap如何限定层级? 比方一张1024x1024纹理,共有Mipmap0~10,然而我的项目只用到Mipmap0~2,如何省略掉3~10? 针对以上问题,有教训的敌人欢送转至社区交换分享:https://answer.uwa4d.com/question/65c0a61340a8d93b624afccc AudioQ:FMOD的设置中,我发现有两个设置选项:Virtual Channel Count和Real Channel Count。想求教一下,个别游戏我的项目内这两个值要设置多少才适合?我发现外网有人都是拉满的,但这会造成CPU累赘。只晓得Real Channel Count这个不能太高。所以想理解下通常这俩参数设置多少比拟正当。 下图来自外网论坛,Virtual Channel Count设置为512、Real Channel Count设置为128,而我的我的项目中这俩设置为128、32。 针对以上问题,有教训的敌人欢送转至社区交换分享:https://answer.uwa4d.com/question/65c0b00840a8d93b624afccd 封面图来源于网络 明天的分享就到这里。生有涯而知无涯,在漫漫的开发周期中,咱们遇到的问题只是冰山一角,UWA社区愿伴你同行,一起摸索分享。欢送更多的开发者退出UWA社区。 UWA官网:www.uwa4d.comUWA社区:community.uwa4d.comUWA学堂:edu.uwa4d.com官网技术QQ群:465082844

February 20, 2024 · 1 min · jiezi

关于memory:故障分析-数据库服务器内存不足一例分析

作者:付祥 现居珠海,次要负责 Oracle、MySQL、mongoDB 和 Redis 保护工作。 本文起源:原创投稿 *爱可生开源社区出品,原创内容未经受权不得随便应用,转载请分割小编并注明起源。 景象监控告警某台机器闲暇内存低于10%,执行top命令,按内存降序排序,局部输入如下: [root@mysql-slaver ~]# toptop - 13:45:43 up 1835 days, 20:52, 2 users, load average: 0.02, 0.03, 0.05Tasks: 210 total, 1 running, 208 sleeping, 1 stopped, 0 zombie%Cpu(s): 0.5 us, 0.6 sy, 0.0 ni, 98.9 id, 0.0 wa, 0.0 hi, 0.0 si, 0.0 stKiB Mem : 32780028 total, 905684 free, 19957900 used, 11916444 buff/cacheKiB Swap: 0 total, 0 free, 0 used. 3448260 avail Mem PID USER PR NI VIRT RES SHR S %CPU %MEM TIME+ COMMAND 2677 mysql 20 0 20.1g 15.1g 3392 S 0.0 48.2 430:17.58 mysqld 10549 polkitd 20 0 3277476 3.1g 632 S 0.3 9.9 146:47.24 redis-server 18183 root 20 0 877308 215868 1892 T 2.7 0.7 2736:45 xxxxxx 442 root 20 0 160244 93016 88552 S 0.3 0.3 314:14.86 systemd-journal 32537 root 20 0 731620 58360 54588 S 0.3 0.2 29:09.61 rsyslogdtotal=32G,used=19G,buff/cache=11G,available=3G,最耗内存过程为 MySQL、Redis,总计约18.2G,其余过程占用内存都比拟低,buff/cache 内存中只有3G是无效的,残余8G内存去哪里? ...

April 18, 2023 · 3 min · jiezi

关于memory:TMP耗时较高的优化问题

1)TMP耗时较高的优化问题2)Unity重载Object后,如何断定物体是否为空3)SRP Batch在增加unity_SpecCube后的问题4)堆内存会持续上升,如何用UWA报告来剖析 这是第326篇UWA技术常识分享的推送,精选了UWA社区的热门话题,涵盖了UWA问答、社区帖子等技术知识点,助力大家更全面地把握和学习。 UWA社区主页:community.uwa4d.comUWA QQ群:465082844 TextMeshProQ:咱们我的项目中UGUI的Canvas.SendWillRenderCanvases耗时高时常常看到子节点TMP.GenerateText和TMP Parse Text耗时高。根本能定位到是角色对话字母跳字的时候,然而这是硬需要不好改。大佬们有没有什么思路优化一下? A1:倡议独自给这块开Canvas。感激欧月松@UWA问答社区提供了答复 A2:角色对话时字幕上一个一个字蹦出来的确是比拟常见的需要了,都是在一个Text组件里变动,而且字越多顶点数就越高,耗时就比拟可观了。 我想到的一个思路还是改用动态字体图集。可参考:https://answer.uwa4d.com/question/63e30f3b0638540599016732 我之前也遇到这个问题,所以做了下试验:在一个Text组件里用代码管制间断输入3000个中文字符集中各不相同的文字。在Profiler中察看到: 当应用动静字体时,每输入一个字符都会在相应的TMP动静图集纹理资源中新画入一个字符,在Unity 2021、TMP 3.0.6版本环境下,输入到最初几个字符时,Canvas.SendWillRenderCanvases耗时来到24ms左右。过后在用动态字体时,同样的输出形式和环境下,输入到最初几个字符时,Canvas.SendWillRenderCanvases耗时升高到14ms左右。在还有一些补充测试中,耗时差距在真机上被进一步放大;而且在一些较低的稳固版本的Unity和TMP环境中试验后果也差不多。这里就不一一列进去了。总之,足以阐明应用动态字体计划在耗时层面也是具备优越性的。 感激Faust@UWA问答社区提供了答复 ScriptQ:Unity重载Object的 == 后,如何真的断定物体是否为空? Unity中还找不到除了Try Catch外,去辨别Destroyed的物体和null的区别。一些资源追踪操作,能够查看Destroyed的物体,比方:取得Destroyed物体的InstanceID,去辨认到底具体出问题的货色是什么。然而如果应用真null的物体调用这类接口,就会出现异常。 当初想找一个,不触发异样的洁净办法,断定一个物体是被捣毁,还是真null。 A:简略的写法:真null的判断:(go as object) == null;如果不是真null,能够持续判断是不是Destroyed:(go as UnityEngine.Object) == null。该答复由UWA提供 RenderingQ:SRP Batch在增加unity_SpecCube相干参数后,呈现 "builtin property offset in cbuffer overlap other stages"。 其实是之前问题的具体情况。在反对SRP Batch的Shader增加反射探针相干参数后:如果把探针参数放在UnityPerDraw外面,测试多种参数程序,都会呈现 "builtin property offset in cbuffer overlap other stages"问题;然而放在UnityPerDraw外,又会呈现提醒要求把相干参数放进外面去。 当初集体想法是,这些参数应该放进UnityPerDraw内,然而具体程序有问题。不晓得有没有胜利植入测试的例子。或者是对源码有了解的兄弟能够给出一个可行的程序。 具体呈现问题的版本是2020.3.16f1。 我的项目现有问题倒是通过将相干参数放在CBuffer外,外加强制裁减变体,在测试到的环境中解决了。然而还是心愿可能解决这个深层次的坑。 A:明天也遇到这个问题了,看了下源码,这些Built-in Feature,例如反射探针或者Motion Vector,Unity是有一个Feature List来解决的,每当你开启一个Feature,会在UnityPerDraw调配一个Block,而后你新退出的参数必须跟这个Block大小匹配。 我的问题是Motion Vector只用到了一个矩阵和一个float4,然而Unity给调配的大小就是两个矩阵+一个float4,所以如果要不影响合批,就得这么申明。 你的状况感觉能够增加到probeVolume这个Feature的Block里,Catlike教程里也有提过,测了下程序不要紧,大小统一就行,3个float4和一个矩阵。 感激xltqM7stGjuG@UWA问答社区提供了答复 MemoryQ:大佬们问一下,咱们我的项目的堆内存会持续上升到400多MB,这个值太高了,而UWA报告中无论是平均分配值乘以帧数还是泄露剖析中的驻留量尽管也很高,然而离400MB还有一些差值。到这里不晓得怎么持续剖析了,有没有好的倡议? A:2022年底帮一个我的项目解决了内存始终涨的问题,起因也是内存碎片。刚开始认为是资源出的问题,始终用Memory Profiler在定位问题,只能看到Empty Heap Space始终在涨,到最初发现这个工具找不到起因,然而大略确定了是内存碎片太多。最初通过GC Allocated的变动定位了局部性能。 在这个过程中发现了Profiler里的波形和数值有时候是对不上的,明明有一个很大的波峰,但数据显示基本不对,这时通过这条波峰是定位不到性能代码的。 ...

March 2, 2023 · 1 min · jiezi

关于memory:在Runtime下IL2CPP与Mono打包对应的PSS内存占用问题

1)在Runtime下,IL2CPP与Mono打包对应的PSS内存占用问题2)取得AssetBundle外部依赖关系的办法3)Unity 2019 Streaming Mipmap在某些状况下采样等级谬误4)依据RenderDoc的数据,计算渲染量 这是第322篇UWA技术常识分享的推送,精选了UWA社区的热门话题,涵盖了UWA问答、社区帖子等技术知识点,助力大家更全面地把握和学习。 UWA社区主页:answer.uwa4d.comUWA QQ群:465082844 MemoryQ:IL2CPP打包比Mono打包在Runtime下跑同样的案例PSS内存占用更高一些,请问是正当的吗? A:揣测可能是IL2CPP包是64位的,而Mono包是32位的,人造内存占用就会高一些,另外IL2CPP在打包的时候,会把各种泛型的代码给开展,所以对应的代码so加载进内存,内存占用也会高一些。 上面是同样的测试案例,两种打包形式的PSS占用截图。 Mono包: IL2CPP包: 从上图中能够看到,IL2CPP包比Mono包内存占用更多的体现在Code、Native Heap和Private Other三个类别上。 感激Xuan@UWA问答社区提供了答复 AssetBundleQ:取得AssetBundle外部依赖关系的办法。注:Manifest的依赖关系不精确。 之前发现从Manifest外面取得的依赖关系在很多状况下都不精确。 比拟显著的是,当Prefab嵌套时,Manifest数据会显示Prefab依赖被嵌套的Prefab;而SpriteAtlas和Sprite依赖关系是反的。其余还有很多状况会额定依赖不须要的资源。用WebExtract解开AssetBundle自身,外面的数据是正确的依赖数据。 因而,当初想找到一个办法,脱离Manifest,自行建设依赖关系表。 当初的问题在于,如果对每个输入的AssetBundle执行解包和正则剖析,会破费大量工夫,并且自身文本匹配也有危险。 有没有什么洁净的流程,能够取得AssetBundle外部记录的依赖关系? A:用UnityEditor.Build.Content.ContentBuildInterface上面的办法获取到的依赖信息是精确的。如上面两个函数:ContentBuildInterface.GetPlayerDependenciesForObjectsContentBuildInterface.CalculatePlayerDependenciesForScene 如果用SBP,Manifest能够在编译实现后加个Task读m_DependencyData,本人建设一个。或者间接用ContentBuildInterface预结构的也能够满足了。 感激题主欧月松@UWA问答社区提供了答复 TextureQ:游戏应用了Mipmap技术,同时为了节俭内存开启了流式加载Streaming Mipmap: 然而在做热更模型的时候,发现某些贴图的Mipmap会采样一个很低的值(很糊),而后即使调整摄像机远近也没有变动(不能动静采样了)。 失常打包进去的资源没有问题(贴图在一个独立Bundle里),但热更打出了的资源就有问题(模型、材质、贴图等都在一个Bundle里),而且贴图占用内存未达到Budget下限。 不晓得问题出在哪了,该如何解决? A:依据形容剖析,有可能是加载了贴图之后把Bundle卸载了。把贴图放在独自的Bundle外面的时候,可能这个Bundle没有被卸载;但在热更的时候,贴图和模型、材质等放在同一个Bundle,可能用代码加载这个Bundle之后又卸载了,就会呈现这个问题。 Streaming零碎刚开始加载的时候会加载分辨率较低的一层Mipmap,之后依据间隔动静调整,如果此时曾经把贴图所在的Bundle卸载了,就无奈依据摄像机间隔调整采样的Mipmap层级。 感激龙粲@UWA问答社区提供了答复 RenderingQ:最近在剖析几款游戏的特效。曾经用RenderDoc抓了几帧。也找到了渲染粒子特效的Pass。 当初问题,是否从顶点Buffer中剖析出粒子的数量?或者有无别的办法估算出粒子的数量? A:如下图,如果是四边形的粒子,比方billboard,66/3=22个三角形,22/2=11个粒子。 感激Xuan@UWA问答社区提供了答复 封面图来源于网络 明天的分享就到这里。当然,生有涯而知无涯。在漫漫的开发周期中,您看到的这些问题兴许都只是冰山一角,咱们早已在UWA问答网站上筹备了更多的技术话题等你一起来摸索和分享。欢送酷爱提高的你退出,兴许你的办法恰能解他人的当务之急;而他山之“石”,也能攻你之“玉”。 官网:www.uwa4d.com官网技术博客:blog.uwa4d.com官网问答社区:answer.uwa4d.comUWA学堂:edu.uwa4d.com官网技术QQ群:465082844

February 1, 2023 · 1 min · jiezi

关于memory:如何优化so-mmap内存占用

1)如何优化.so mmap内存占用2)模拟器下物理碰撞生效3)Unity RenderTexture的开释在安卓上并不能使GL内存齐全回落4)数字人中,怎么做到胡子固定在嘴巴皮肤上 这是第319篇UWA技术常识分享的推送。明天咱们持续为大家精选了若干和开发、优化相干的问题,倡议浏览工夫10分钟,认真读完必有播种。 UWA 问答社区:answer.uwa4d.comUWA QQ群:465082844 MemoryQ:我的项目PSS内存过高,应用adb指令抓取内存形成,发现.so mmap局部达到了180MB。这块有没有优化倡议? A1:能够从以下三个方向查一下: 泛型Template用的是否较多自动化生产的代码是否较多Lua生产的Wrapper代码是否较多感激小ben@UWA问答社区提供了答复 A2:还有以下两点: 删除无用插件、代码IL2CPP是否进行裁剪感激郑骁@UWA问答社区提供了答复 PhysicsQ:在雷电模拟器下,有大概率车与动态创建物体没碰撞,然而很奇怪的是某些雷同Layer有的能够碰撞。 前面间接空场景打包模拟器运行,创立cube + rigidbody赋值不同Layer模仿,有时候是有的Layer之间全副没有碰撞,还会呈现同一个运行每次会碰撞的Layer都不一样。 查了好久没啥思路,有没有大佬遇到类似问题? 以上问题有哪位大拿也已经做过相似的测试,欢送转至社区交换分享:https://answer.uwa4d.com/ques... MemoryQ:Unity RenderTexture的开释在安卓上并不能使GL内存齐全回落? Unity版本:2020.3.21.f1测试机:HUAWEI P30内存统计工具:Perfdog 问题详情:状况1:创立RT并立即卸载,此时GL内存齐全回落。状况2:创立RT不卸载,此时GL回升并稳固。再创立RT并立即卸载,此时GL回升,但回落时(也有可能不回落)不会回到之前地位,会有一部分残留,相似于透露的状况。想请问下为什么会产生这种状况? 我搭建了一个测试场景,次要测试了两个函数,第一个函数只生成RT不卸载、第二个函数生成RT并立即卸载。 在真机上测试后果: 能够看到前三次,调用办法2对应了状况1,此时内存回落失常。但在调用办法1后再调用办法2,则会产生内存无奈回落到原地位的状况。 目前查到的材料:参考了UWA上的一篇文章《分享一次查找GfxDriver内存暴涨的经验》 外面是这样解释的: 对于GL显存,外面说到回落不到原地位的起因是只卸载了Main Memory,而不卸载Pinned Memory,那有什么办法卸载掉Pinned Memory使内存齐全回落的办法吗? 我对Main Memory和Pinned Memory具体是什么不太理解,想求教一下大家。 以上问题有哪位大拿也已经做过相似的测试,欢送转至社区交换分享:https://answer.uwa4d.com/ques... AnimationQ:数字人中,怎么做到胡子固定在嘴巴皮肤上?口型管制利用时,胡子怎么跟着嘴巴皮肤同步静止变形? A:在数字人模型中,胡子的静止变形能够通过在嘴巴皮肤模型上增加骨骼来实现。首先,您须要在嘴巴皮肤模型上创立一些骨骼,并将它们附着在相应的地位。而后,您能够应用骨骼动画性能,在口型管制时通过管制骨骼的静止来实现胡子的追随变形。 为了保障胡子可能固定在嘴巴皮肤上,您能够在创立骨骼时,在骨骼根部和嘴巴皮肤模型之间增加控制点(Control Point)。控制点能够用来连贯骨骼和嘴巴皮肤,并使得骨骼的静止对嘴巴皮肤产生影响。通过增加控制点,您就能够在保障胡子固定的同时,让胡子可能随着嘴巴皮肤的变形而变形。 感激Hyhom@UWA问答社区提供了答复 封面图来源于网络 明天的分享就到这里。当然,生有涯而知无涯。在漫漫的开发周期中,您看到的这些问题兴许都只是冰山一角,咱们早已在UWA问答网站上筹备了更多的技术话题等你一起来摸索和分享。欢送酷爱提高的你退出,兴许你的办法恰能解他人的当务之急;而他山之“石”,也能攻你之“玉”。 官网:www.uwa4d.com官网技术博客:blog.uwa4d.com官网问答社区:answer.uwa4d.comUWA学堂:edu.uwa4d.com官网技术QQ群:465082844

January 5, 2023 · 1 min · jiezi

关于memory:MongoDB-查看索引与存储大小qbit

前言本文试验环境为 3 台机器的 MongoDB 正本集Using MongoDB: 5.0.5Using Mongosh: 1.1.8官网文档:https://www.mongodb.com/docs/...注释列出所有数据库> db.getMongo().getDBs()查看单个库的统计信息以下统计信息是每台机器上的统计信息,不是3台机器的和。totalSize = storageSize + indexSize> db.getMongo().getDB('pubmedjournal').stats(){ db: 'pubmedjournal', collections: 3, views: 0, objects: 44928355, avgObjSize: 36032.49960482639, dataSize: 1618880933783, storageSize: 510386745344, // 每台机器上document占用磁盘空间总大小 freeStorageSize: 12275736576, indexes: 13, indexSize: 8714760192, // 每台机器上index占用磁盘空间总大小 indexFreeStorageSize: 993247232, totalSize: 519101505536, // 每台机器上占用磁盘空间总大小 totalFreeStorageSize: 13268983808, scaleFactor: 1, fsUsedSize: 9056225865728, fsTotalSize: 11680169852928, ok: 1, '$clusterTime': { clusterTime: Timestamp({ t: 1667097728, i: 16 }), signature: { hash: Binary(Buffer.from("c787785abf50320f6620c282fbb5b1e080304032", "hex"), 0), keyId: Long("7106121282126610433") } }, operationTime: Timestamp({ t: 1667097728, i: 16 })}列出单库下的 collection> db.getMongo().getDB('pubmedjournal').getCollectionNames()[ 'publisher', 'article_data', 'article' ]单个 collection 的统计信息> collStats = db.getMongo().getDB('pubmedjournal').getCollection('article').stats()> collStats.storageSizeLong("496281411584")> collStats.totalIndexSizeLong("6650699776")> collStats.totalSizeLong("502932111360")正本集上所有数据库的统计之和留神这里也是1份数据的统计值,正本集3份数据之和。 ...

October 30, 2022 · 1 min · jiezi

关于memory:ParticleSystem的内存会受到MaxParticles影响吗

1)ParticleSystem的内存会受到MaxParticles影响吗2)应用代码批改图片压缩格局报错3)Unity纹理导入设置对内存影响4)Unity中视频播放的解决方案 这是第285篇UWA技术常识分享的推送。明天咱们持续为大家精选了若干和开发、优化相干的问题,倡议浏览工夫10分钟,认真读完必有播种。 UWA 问答社区:answer.uwa4d.comUWA QQ群2:793972859(原群已满员) MemoryQ:ParticleSystem的内存会受到MaxParticles设置的数值影响吗? A:在真机上做了一个测试,Unity 2020.3.17,小米9。测试的特效是Unity默认的ParticleSystem特效,后果如下: 从下面的图上能够看到,内存受到“理论最多粒子数量”的影响,(能够大概了解为持续时间*每秒发射数量,不思考Burst的发射形式)。当发射数量为0时,ParticleSystem也是有9416Byte的内存占用,这个是Unity内置的各种序列化的货色的占用。 另外,当“理论最多粒子数量”确定后,Active的粒子数随着播放工夫增长越来越多,内存占用是不会变的,如下图所示。 PS:当ParticleSystem发射的是Mesh时,会造成Gfx内存回升,且这个Mesh内存占用是不统计到Assets/Mesh内存占用中的。 感激Xuan@UWA问答社区提供了答复 TextureQ:想新增图片时,自动化解决图片的设置,后果设置图片时报错了,网上也没有找到解决方案,谬误如下:Selected texture format ‘Unsupported’ for platform ‘Android’ is not valid with the current texture type ‘Sprite’。 Unity版本:2019.4.34f1 public class TextureImporterProcessor : AssetPostprocessor{ void OnPostprocessTexture(Texture2D texture) { bool haveAlpha = textureImporter.DoesSourceTextureHaveAlpha(); textureImporter.isReadable = false; textureImporter.mipmapEnabled = false; textureImporter.alphaIsTransparency = haveAlpha; textureImporter.textureType = TextureImporterType.Sprite; textureImporter.textureCompression = TextureImporterCompression.Compressed; TextureImporterPlatformSettings androidSettings = new TextureImporterPlatformSettings() { name = "Android", overridden = true, format = haveAlpha ? TextureImporterFormat.ASTC_RGBA_6x6 : TextureImporterFormat.ASTC_RGB_6x6, }; textureImporter.SetPlatformTextureSettings(androidSettings); TextureImporterPlatformSettings iOSSetting = new TextureImporterPlatformSettings() { name = "iPhone", overridden = true, format = haveAlpha ? TextureImporterFormat.ASTC_RGBA_6x6 : TextureImporterFormat.ASTC_RGB_6x6, }; textureImporter.SetPlatformTextureSettings(iOSSetting); textureImporter.SaveAndReimport(); }}A1:可能是纹理格局的问题,我这边把ASTC_RGBA_6x6改成ASTC_6x6就没有这个报错了。感激宗卉轩@UWA问答社区提供了答复 ...

February 14, 2022 · 1 min · jiezi

关于memory:UGUI和粒子特效的穿插使用问题

1)UGUI和粒子特效的交叉应用问题2)我的项目导入多个Spine动画合批后升高DrawCall问题3)Font Texture占用内存问题4)Unity Texture Streaming的疑难 这是第261篇UWA技术常识分享的推送。明天咱们持续为大家精选了若干和开发、优化相干的问题,倡议浏览工夫10分钟,认真读完必有播种。 UWA 问答社区:answer.uwa4d.comUWA QQ群2:793972859(原群已满员) UGUIQ:UGUI中粒子在滑动列表中应用时,须要将特效放在两个Image两头展现,如果将高低两个Image与特效都加Canvas后,滑动列表的Mask就不会起作用,有什么好方法吗? A1:举荐一个不错的开源我的项目:Particle Effect For UGUI感激张首峰@UWA问答社区提供了答复 A2:我也举荐一个不错的计划:UIEffect感激马三小伙儿@UWA问答社区提供了答复</font> RenderingQ:我的项目中应用到了Spine插件,在场景中应用了大量的Spine动画,每一个动画就是一个DrawCall,场景应用60个动画就是同时存在60个DrawCall,有没有方法将其合批到一次或者几次绘制? Spine动画都是这样的: A:这里提供一种可能可行的方法:我的项目降级到URP,应用SRP Batcher对Spine进行合批。 首先在内置的渲染管线中,我在场景中复制了完全相同的10个的Binary导出的Spine和10个Json导出的Spine。如图,连齐全Copy的Spine动画都无奈合批: 那URP如何操作呢?不是所有的对象都能够应用SRP Batcher来渲染,Shader必须与SRP Batcher兼容。但默认的URP工程中并没有封装Spine应用的Shader。所以,Spine官网提供了对URP的反对:http://zh.esotericsoftware.co...。 下载官网提供的UPM包、通过PackageManager装载到应用Spine的URP我的项目中: 在URP的我的项目里,把测试用例稍作批改:我导入了两个图集、材质等因素齐全不同的Spine(Spineboy和Hero),来测试不同的Spine能不能被SRP Batcher合批。 资源导入结束后,把原来Spine资源的Shader改为URP版本的Shader。如下图,“Spine/Skeleton”改为了“Universal Render Pipeline/Spine/Skeleton”: 批改完后,查看Frame Debugger,所有的Spine的渲染都合并成一个Batch了,阐明SRP Batcher岂但能合批反复的Spine动画,更能使齐全不同的Spine动画合批。 感激Faust@UWA问答社区提供了答复 MemoryQ:Font Texture占用内存问题:真机连贯Profiler、真机连贯RenderDoc和Unity运行Profiler对应的Font Texture不统一,Unity版本:2018.2.16f1: Unity运行Profiler: 真机连贯Profiler: Unity连贯Profiler1: 真机连贯RenderDoc1: A:因为我近期在做一些纹理相干的试验时发现局部Unity版本会使真机上Alpha8格局的纹理大小不合乎计算结果: 多台手机、多个版本的测试比照,看得出这种异样产生在了一些特定手机中(其余版本也有产生轻微内存变动的,但远没有2018.2版本幅度这么大)。 按理来说1024×1024分辨率的Alpha8格局纹理大小应为1MB,然而真机上是4MB。我测试的其余版本(2018.4、2019.2、2019.4、2020.3)中则失常为1MB。且Mipmap、Read/Write等选项的开关也不影响内存翻了4倍的状况。 联合本帖的问题,我做了如下比拟试验(真机上的内存数据我用了UWA GOT Online Assets运行时资源检测服务,能够清晰看到内存的小数点后两位)。 场景中应用了一张1024×1024 Alpha8纹理和原大小为0.5MB的Font Texture字体纹理。 2018.4版本编辑器中:1024×1024 Alpha8纹理 2MBFont Texture 0.5MB 真机上:1024×1024 Alpha8纹理 1MBFont Texture 0.5MBFont Texture 0.125MB 2018.2版本编辑器中:1024×1024 Alpha8纹理 2MBFont Texture 0.5MB 真机上:1024×1024 Alpha8纹理 4MBFont Texture 1.25MB(0.5MB×2.5)Font Texture 0.32MB(0.125MB×2.5) ...

August 4, 2021 · 1 min · jiezi

关于memory:Python3-内存文件qbit

io.StringIO/io.BytesIO官网文档: https://docs.python.org/3/lib...tempfile.TemporaryFile/tempfile.TemporaryDirectory官网文档: https://docs.python.org/3/lib...pyfilesystem官网文档: https://docs.pyfilesystem.org/本文出自 qbit snap

June 20, 2021 · 1 min · jiezi

关于memory:Addressable-RemoteBuildPath下部分资源更新上传问题

1)Addressable RemoteBuildPath下局部资源更新上传问题2)Shader内存占用忽然变大3)Unity 2018.4上材质对贴图的冗余援用4)编辑器中呈现大量GC操作导致卡顿5)JsonUtility.ToJson浮点型保留小数问题 这是第251篇UWA技术常识分享的推送。明天咱们持续为大家精选了若干和开发、优化相干的问题,倡议浏览工夫10分钟,认真读完必有播种。 UWA 问答社区:answer.uwa4d.comUWA QQ群2:793972859(原群已满员) AddressableQ:Addressable设置好RemoteBuildPath门路后,Remote_Group(该Group下有多个资源包)下批改了一个资源包,Build->Update a previous build 完结后,RemoteBuildPath门路下将Remote_Group的所有资源包都从新生成了一遍(将所有的资源包从该门路下删除,再执行时还是会全副生成)。因为要将Remote_Group下的资源包上传到服务器,只心愿上传产生扭转的Remote资源,不心愿反复上传所有的资源包,该怎么操作呢? A:尽管全副从新生成,然而应该只有批改了的那份是不一样的,能够比照一下看看hash文件里记录的hash有没有变动。其实在服务器上间接都上传就行了,客户端会依据hash的不同下载变动了的Bundle。这种模式能保障服务器上的是最新的,客户端不论哪个版本都只有下载差别Bundle就行了。感激黄程@UWA问答社区提供了答复 A2:用了一个折中的方法,每次上传前计算已存在文件的md5,新的文件和md5值发生变化的文件上传至服务器。感激题主猴小样2016@UWA问答社区提供了答复 MemoryQ:我的项目Unity版本升级后,Shader内存占用变为原先的100倍,请问是什么起因导致的? A:这个应该是因为Unity 2019.4.20这个版本当前批改了Shader内存占用的统计形式。Unity这样改,不便大家间接看到是哪个Shader占用高,从而更好地定位问题。能够看到在版本升级后,Shader占用120多MB,ShaderLab变成了92KB。 2019.4.20f1 Release Notes 感激芭妮妮@UWA问答社区提供了答复 MaterialQ:打包后应用AssetHunter插件剖析发现有很多冗余援用,然而在游戏中应用Profiler却没有。 关上材质文件发现保留了很多旧的贴图援用信息: 如_CloudTex这个,是旧版本应用过的,新的Shader里都没有这个属性了。个别的查找援用的办法是找到所有关联的GUID,那就会找到这些旧的援用,但在手机上应用Profiler看内存时却没有相干援用的贴图被加载。是否是因为Unity打包时会主动把这些旧援用筛除?只管在Editor上还是找失去这些冗余援用,是否能够不必理睬? A1:旧的Shader应用过的Texture援用会被保留在序列化信息中,这是Unity无意的设计,为了防止编辑器下切换Shader再换回来时须要重新配置的麻烦。 Unity在打包时的确会对材质进行深度查看,把以后Shader没有应用的Texture依赖都去除,因而通常状况下打包旧的援用也不会导致包体变大。 但旧的援用在Editor下还存在一个问题,AssetDatabase.GetDependencies接口返回的援用会包含旧的援用,它不会进行深度检测来进行剔除。右键资源Select Dependencies调用的是这个接口,所以也会显示旧的援用,如下图: pic2和pic3是以后Shader2没有援用的。 所以应用GetDependencies这个接口实现一些性能,可能因为旧援用导致意外的问题。解决办法是:应用EditorUtility.CollectDependencies这个接口返回剔除旧援用的后果。 此外,如果Mat和pic1,pic2,pic3调配别独自打了一个AB包,那么打出的mat.manifest文件中会显示旧的Texture援用,这就是AssetBundle打包的一个不太正当的中央了,还没有调研新的Addressable零碎是否做了改良。 感激Prin@UWA问答社区提供了答复 A2:参考这个帖子:https://forum.unity.com/threads/material-asset-keeps-references-to-assets-that-are-not-used.523192/ 能够间接用外面的CleanUpMaterials来清理材质。 感激小埃拉@UWA问答社区提供了答复 EditorQ:编辑器下AssetDatabase.GetAllAssetBundleNamesWithoutVariant()呈现大量GC操作导致卡顿。 复现步骤:1.游戏外面抉择一个材质,或者其余对象。2.把Unity编辑器切换到后盾再切回来,我这里是间接点开Sublime再切回Unity做完下面两步后,Unity就会变的很卡很卡,通过Profiler剖析EditorLoop能够看到AssetDatabase.GetAllAssetBundleNamesWithoutVariant有大量的GC操作,如下图: 用的是Unity 2019.4.15f1,上面是我尝试过的解决方案:1.狐疑Unity版本问题,可是笔记本外面装置的同版本Unity新建工程是复现不了。2.狐疑OnInspectorGUI有问题,而后把代码外面的所有OnInspectorGUI正文了,也还是能复现。3.狐疑ShaderGUI有问题,把所有ShaderGUI相干代码删了,一样能复现。 有大佬能够提供其余思路去排查这个问题吗?咱们当初长期解决办法是,切换一下选中对象就不卡了。 A:我的项目中是通过设置AssetImporter的bundleName来标记Bundle的吗? importer.assetBundleName = bundleName;importer.assetBundleVariant = variant;查看了一下编辑器局部的源码,发现AssetDatabase.GetAllAssetBundleNamesWithoutVariant();这个API是在绘制上面这部分的时候调用的。猜想如果BundleName和BundleVariant较多,会不会引起卡顿? Q:咱们我的项目导入资源的时候是有只设置了Bundle Name,也就是上面的设置形式 importer.assetBundleName = bundleName; 我的项目是没有设置BundleVariant,不过的确有不少BundleName,对于BundleName数量官网有下限要求? A:你能够试一下在代码中手动调用一下AssetDatabase.GetAllAssetBundleNamesWithoutVariant()(internal办法,通过反射调用),看看是不是会造成大量GC和卡顿。如果本人调用也会造成卡顿,八成就是这个引起的。如果就是这个Bundle Name这个过多因而的卡顿,那你就用AAB的形式打bundle就好了,别设置importer.assetBundleName。感激马三小伙儿@UWA问答社区提供了答复 ScriptQ:在应用JsonUtility.ToJson时,保留float类型时,提前曾经做好保留两位小数,但ToJson后果1.059999999999999都是这样的。请问有解决办法吗? A:很遗憾,Unity的JsonUtility是在Native层实现的,是闭源的并且提供的接口性能极少。倡议应用其余Json插件,如Newtonsoft.json:https://github.com/SaladLab/Json.Net.Unity3D感激Prin@UWA问答社区提供了答复 A2:自荐一下魔改版LitJson4Unity。https://github.com/XINCGer/LitJson4Unity LitJson4Unity:实用于Unity的改进型LitJson库简介:基于原生的LitJson库革新的实用于Unity的LitJson库。 反对以下原生版本不反对的个性: 反对float类型(最新原生版本曾经反对)反对Unity内建类型(Vector2、Vector3、Rect、AnimationCure、Bounds、Color、Color32、Quaternion、RectOffset等)反对JsonIgnore Attritube,对某些字段跳过序列化反对对输入的Json内容格式化,更规整应用办法:间接将 Plugins/LitJson目录下所有的cs脚本放到你的工程中即可。 【Unity游戏开发】跟着马三一起魔改LitJsonhttps://www.cnblogs.com/msxh/p/12541159.html 感激马三小伙儿@UWA问答社区提供了答复 封面图来源于网络 明天的分享就到这里。当然,生有涯而知无涯。在漫漫的开发周期中,您看到的这些问题兴许都只是冰山一角,咱们早已在UWA问答网站上筹备了更多的技术话题等你一起来摸索和分享。欢送酷爱提高的你退出,兴许你的办法恰能解他人的当务之急;而他山之“石”,也能攻你之“玉”。 官网:www.uwa4d.com官网技术博客:blog.uwa4d.com官网问答社区:answer.uwa4d.comUWA学堂:edu.uwa4d.com官网技术QQ群:793972859(原群已满员)

May 28, 2021 · 1 min · jiezi

关于memory:厚积薄发Instruments如何看Mono内存分配

1)Instruments如何看Mono内存调配 2)对于Addressable v1.11.2的疑难 3)开展UV2时导致Mesh顶点数减少 4)晋升Unity编辑器中代码的编译速度 5)Renderdoc调试的疑难 这是第217篇UWA技术常识分享的推送。明天咱们持续为大家精选了若干和开发、优化相干的问题,倡议浏览工夫10分钟,认真读完必有播种。 UWA 问答社区:answer.uwa4d.com UWA QQ群2:793972859(原群已满员) MemoryQ:例如在调配了一个10MB数组,对应在Unity Profiler中会看到开拓了至多10MB大小的Mono内存。 那么在Instruments中,如何查看调配的内存信息呢?Allocations中的信息是此过程中调配的所有内存信息吗,尝试调配过100MB内存,Allocations中的统计没有任何增长。 A:我这边也做了测试: 创立了100MB大小的int数组,Size理论应该是400MB。 而后到Profile察看: 能够看到ManagedHeap正确调配了这400MB的空间。 而后打包iOS后到Xcode运行,运行前首先把Run这个Scheme的Malloc Stack勾上。 RUN当前点选Memory并导出Memory Graph来察看: 因为应用程序的内存都是在VirtualMemory空间调配的,因而查看VM Regions的VM_ALLOCATE局部。 能够发现128X3+16刚好400MB的调配。 调用堆栈也很好确定: 正是咱们的测试代码。 而后咱们来看Instruments。首先是Allocations局部,有一点要留神,该栏的下部有一些选项: 留神最初一个选项,如果抉择第一个: All Heap & Anonymous VM,All Heap对应App理论调配的物理空间,不蕴含VM,Anonymous VM的官网解释是: interesting VM regions such as graphics- and Core Data-related. Hides mapped files, dylibs, and some large reserved VM regions。 因而一些比拟大的预留调配空间是不会显示的。 ...

September 10, 2020 · 1 min · jiezi

关于memory:厚积薄发Android-10系统下的PSS数值统计不准

1)Android 10零碎下的PSS数值统计不准 2)Memory Profiler中的类型内存大小计算 3)Addressable加载Bytes文件在手机上报错 4)应用SBP打Bundle,如何读取AssetBundleManifest 5)GameObject如何开释从Bundle中加载的Asset 这是第215篇UWA技术常识分享的推送。明天咱们持续为大家精选了若干和开发、优化相干的问题,倡议浏览工夫10分钟,认真读完必有播种。 UWA 问答社区:answer.uwa4d.com UWA QQ群2:793972859(原群已满员) MemoryQ:从下图测试的后果来看,Android 10.0的PSS的内存值是平的,没有任何变动。但如果用Android 9.0版本的测试机测试,数值就是失常。初步猜想这个就是Android 10的内存反馈,但到底是否为Bug还不确定。有遇到雷同状况的小伙伴吗? A:同样被Android 10坑了,来答复一下起因: ActivityManager的 public Debug.MemoryInfo[] getProcessMemoryInfo(int[] pids)变了。 /** <p>As of {@link android.os.Build.VERSION_CODES#Q Android Q}, for regular apps this method* will only return information about the memory info for the processes running as the* caller's uid; no other process memory info is available and will be zero.* Also of {@link android.os.Build.VERSION_CODES#Q Android Q} the sample rate allowed* by this API is significantly limited, if called faster the limit you will receive the* same data as the previous call.</p>*/查了一下安卓源码,这个值竟然默认是5分钟,也就是说5分钟更新一次,所以看起来是平的。 ...

August 12, 2020 · 1 min · jiezi

实战Go内存泄露

最近解决了我们项目中的一个内存泄露问题,事实再次证明pprof是一个好工具,但掌握好工具的正确用法,才能发挥好工具的威力,不然就算你手里有屠龙刀,也成不了天下第一,本文就是带你用pprof定位内存泄露问题。 关于Go的内存泄露有这么一句话不知道你听过没有: 10次内存泄露,有9次是goroutine泄露。我所解决的问题,也是goroutine泄露导致的内存泄露,所以这篇文章主要介绍Go程序的goroutine泄露,掌握了如何定位和解决goroutine泄露,就掌握了内存泄露的大部分场景。 本文草稿最初数据都是生产坏境数据,为了防止敏感内容泄露,全部替换成了demo数据,demo的数据比生产环境数据简单多了,更适合入门理解,有助于掌握pprof。go pprof基本知识定位goroutine泄露会使用到pprof,pprof是Go的性能工具,在开始介绍内存泄露前,先简单介绍下pprof的基本使用,更详细的使用给大家推荐了资料。 什么是pprofpprof是Go的性能分析工具,在程序运行过程中,可以记录程序的运行信息,可以是CPU使用情况、内存使用情况、goroutine运行情况等,当需要性能调优或者定位Bug时候,这些记录的信息是相当重要。 基本使用使用pprof有多种方式,Go已经现成封装好了1个:net/http/pprof,使用简单的几行命令,就可以开启pprof,记录运行信息,并且提供了Web服务,能够通过浏览器和命令行2种方式获取运行数据。 看个最简单的pprof的例子: 文件:golang_step_by_step/pprof/pprof/demo.go package mainimport ( "fmt" "net/http" _ "net/http/pprof")func main() { // 开启pprof,监听请求 ip := "0.0.0.0:6060" if err := http.ListenAndServe(ip, nil); err != nil { fmt.Printf("start pprof failed on %s\n", ip) }}提醒:本文所有代码部分可左右滑动 浏览器方式 输入网址ip:port/debug/pprof/打开pprof主页,从上到下依次是5类profile信息: block:goroutine的阻塞信息,本例就截取自一个goroutine阻塞的demo,但block为0,没掌握block的用法goroutine:所有goroutine的信息,下面的full goroutine stack dump是输出所有goroutine的调用栈,是goroutine的debug=2,后面会详细介绍。heap:堆内存的信息mutex:锁的信息threadcreate:线程信息这篇文章我们主要关注goroutine和heap,这两个都会打印调用栈信息,goroutine里面还会包含goroutine的数量信息,heap则是内存分配信息,本文用不到的地方就不展示了,最后推荐几篇文章大家去看。 命令行方式当连接在服务器终端上的时候,是没有浏览器可以使用的,Go提供了命令行的方式,能够获取以上5类信息,这种方式用起来更方便。 使用命令go tool pprof url可以获取指定的profile文件,此命令会发起http请求,然后下载数据到本地,之后进入交互式模式,就像gdb一样,可以使用命令查看运行信息,以下是5类请求的方式: # 下载cpu profile,默认从当前开始收集30s的cpu使用情况,需要等待30sgo tool pprof http://localhost:6060/debug/pprof/profile # 30-second CPU profilego tool pprof http://localhost:6060/debug/pprof/profile?seconds=120 # wait 120s# 下载heap profilego tool pprof http://localhost:6060/debug/pprof/heap # heap profile# 下载goroutine profilego tool pprof http://localhost:6060/debug/pprof/goroutine # goroutine profile# 下载block profilego tool pprof http://localhost:6060/debug/pprof/block # goroutine blocking profile# 下载mutex profilego tool pprof http://localhost:6060/debug/pprof/mutex上面的pprof/demo.go太简单了,如果去获取内存profile,几乎获取不到什么,换一个Demo进行内存profile的展示: ...

May 18, 2019 · 7 min · jiezi

CPU & Memory, Part 3: Virtual Memory

博文:https://chanjarster.github.io…原文:What every programmer should know about memory, Part 3: Virtual Memory4 Virtual Memory虚拟内存(virtual memory)是处理器的一个子系统,它给每个进程提供虚拟地址空间(virtual address space)。这让每个进程以为自己在系统中是独自一人。wiki词条:虚拟内存的作用在于为进程提供“看上去”连续的地址空间,这么做的好处在于程序不需要处理内存碎片的问题了。虚拟地址空间由CPU的Memory Management Unit(MMU)实现,操作系统必须填写页表数据结构(page table data structures,见wiki词条),大多数CPU自己完成余下的工作。把虚拟地址(virtual address)作为输入交给MMU做翻译。在32位系统中虚拟地址是32位的,在64位系统中是64位的。4.1 Simplest Address TranslationMMU可以逐页(page)的将虚拟地址翻译成物理地址的,和cache line一样,虚拟地址被分割成多个部分,这些部分则被索引到不同的表(table)里,这些表用来构造最终的物理地址。最简单的模型则只拥有一个级别的表(only one level of tables)。Figure 4.1: 1-Level Address Translation虚拟地址的结构:虚拟地址的头部被用来在一个页目录(Page Directory)中选择条目(entry),页目录中存储的是条目(entry),每个条目可由操作系统单独设置。条目决定了物理内存页的地址,即页的物理地址虚拟地址的尾部是页内的偏移量所以页的物理地址+偏移量=物理地址页目录的条目还包含一些辅助信息,比如访问权限页目录的存储:页目录是存在内存里的,操纵系统为其分配一段连续的物理内存空间,并将基地址(base address)存在一个特殊的寄存器里而条目在目录里就是一个数组(记住这是数组,这对于理解下面多级目录,多级索引很重要)先弄个速算表,下面会用得着:29=512210=512 * 2=1024=1K220=1024 * 1024=1MB拿x86系统,4MB页举例:虚拟地址的偏移量部分占用22位(可以覆盖4MB的空间)目录部分则还剩10位,即可以存放1024个条目每个条目存了10位的物理页内存的基地址10位+22位=32位,形成了完整的物理内存地址4.2 Multi-Level Page Tables多级页表(page table),注意原文写到这里用页表(page table)而不是页目录(page directory),这两个实际上是一个东西。上面的例子拿4MB页来举例的,不过4MB页表不常见,这是因为操作系统执行的很多操作是按照页来对齐的,意思是每个页的基地址之间都差4MB的倍数,就算你要用1k内存也要申请了一个4MB的页,这造成了大量的浪费。真实世界里32位机器大多用4kB页,同样多见于64位机器。为啥4kB页,单级页表不行:虚拟地址偏移量占12位虚拟地址页目录部分占20位(64位机器就是52位)页表条目数=220,就算每个条目只占4 bytes(32位)那整个页表页要占4MB然后每个进程会拥有自己的页表,那么大量的物理内存就会被用在页表上。实际上不光是物理内存用量太大的问题,因页表就是一个数组,需要连续的内存空间,到时候很难分配。解决办法是使用多级页表。它们能够代表一个稀疏的巨大的页表,可以做到对没有被使用的区域(原文没有讲区域是啥)不需要分配内存。这种形式跟为紧凑,可以为许多进程提供页表,同时又不对性能产生太大影响。Figure 4.2: 4-Level Address Translation上面是一个4级页表:虚拟地址被分割成5个部分,其中4个部分是不同页表的索引第4级页表通过CPU里的一个特殊目的的register来引用第4级-第2级的页表的内容是对下一级页表引用(我觉得应该就是物理内存地址,因为前面讲过页表存在物理内存中的)第1级页表存储的物理地址的一部分(应该就是去掉偏移量的那一部分)和辅助数据,比如访问权限所以整个形成了一个页表树(page table tree),稀疏又紧凑(sparse and compact)得到物理地址的步骤,Page tree walking:先从register中得到第4级页表的地址,拿到第4级页表拿虚拟地址中Level 4 Index取得页表中的条目,这个条目里存的是第3级页表的地址拿到第3级页表拿虚拟地址中Level 3 Index取得页表中的条目,这个条目里存的是第2级页表的地址如此反复直到拿到第1级页表里的条目,这个条目里存的是物理地址的高位部分结合虚拟地址中的偏移量,得到最终的物理地址Page tree walking在x86、x86-64处理器里是发生在硬件层面的Page table tree尺寸对性能的影响:每个进程可能需要自己的page table tree,几个进程共享树的一部分是存在的,但这只是特例。如果页表树所需内存越小,那就越有利于性能和扩展性(performance and scalability)理想情况下,把使用的内存在虚拟地址空间里紧密的放在一起,就能够让page table tree占用的空间小(单独看这句没有办法明白,结合后面的内容看举例,4kB/页,512条目/页表,1页表/每级,那么可以寻址2MB连续的地址空间(512*4kB=2MB)举例,4kB/页,512条目/页表,4-2级只有1个页表,1级有512个页表,那么可以寻址1GB连续的地址空间(512 512 4KB=1G)Page table tree布局:假设所有内存都能够连续的被分配太过简单了比如,出于灵活性的考虑(flexibility),stack和heap分占地址空间的两端,所以极有可能有2个2级页表,每个二级页表有一个1级页表。显示中比上面这个更复杂,处于安全性考虑,不同的可执行部分(code、data、heap、stack、DSOs又称共享库)是被影射到随机地址上的。所以进程所使用的不同内存区域是遍布整个虚拟地址空间的。所以一个进程不可能只有一两个2级3级页表的。个人总结,前面讲的对于多少连续的寻址空间,各级别页表需要多少个是这么计算的:首先得知道前提,对于4-2级页表,在同一页表内,不同页表条目不会指向同一个下一级页表对于1级页表,不同页表条目不会指向相同的物理地址(准确的说是物理地址去掉offset的部分)对于4-2级页表,每个页表条目指向一个下级页表,即上级页表条目数目=下级页表数假设现在是32位系统,每个页表至多保存29=512个页表项下面举连续的2MB寻址空间(页大小为4kB):2MB=210 210 2=221 bytes所以需要:2MB / 4kB = 221 / 212 = 29个1级页表条目所以需要:29 / 29=1个一级页表=1个2级页表条目所以前面说,4kB/页,512条目/页表,1页表/每级,那么可以寻址2MB连续的地址空间下面举例连续的1GB寻址空间(页大小为4kB):1GB=210 210 210=230 bytes所以需要1级页表条目:1GB / 4kB = 230 / 212=218个1级页表条目所以需要:218 / 29=29个1级页表=29个2级页表条目所以需要:29 / 29=1个2级页表所以前面说,4kB/页,512条目/页表,4-2级只有1个页表,1级有512个页表,那么可以寻址1GB连续的地址空间(512 512 4KB=1G)同理如果是连续的2GB寻址空间(页大小为4kB):1GB=210 210 210 * 2=231 bytes所以需要:1GB / 4kB = 231 / 212=219个1级页表条目所以需要:219 / 29=210个1级页表=210个2级页表条目所以需要:210 / 29=2个二级页表=2个3级页表条目4.3 Optimizing Page Table Access所有页表是存在main memory中的,操作系统负责构建和更新页表创建进程或更新页表时CPU会收到通知页表被用来每一次解析虚拟地址到物理地址的工作,采用的方式是page tree walking当解析虚拟地址的时候,每级都至少有一个页表在page tree walking中被使用所以每次解析虚拟地址要访问4次内存,这很慢TLB:现代CPU将虚拟地址的计算结果保存在一个叫做TLB(Tranlsation Look-Aside Buffer)的cache中。TLB是一个很小的cache,而且速度极快现代CPU提供多级TLB,级别越高尺寸越大同时越慢。也分为数据和指令两种,ITLB和DTLB。高层级TLB比如2LTLB通常是统一的。(和前一篇文章讲的cache结构类似)因为虚拟地址的offset不参与page tree walking,所以使用其余部分作为cache的tag通过软件或硬件prefetch code/data会隐式的prefetch TLB条目,如果地址是在另一个page上时4.3.1 Caveats Of Using A TLB讲了几种优化TLB cache flush的手段,不过没有讲现代CPU使用的是哪一种。个人认为这段不用太仔细读,只需要知道存在一种手段可以最少范围的flush TLB cache entry就行了。4.3.2 Influencing TLB Performance使用大页:页尺寸越大,则页表需要存储的条目就越少,则需要做的虚拟地址->物理地址翻译工作就越少,则需要TLB的条目就越少。有些x86/x86-64支持4kB、2MB、4MB的页尺寸。不过大页存在问题,给大页使用的内存区域必须是连续的。如果物理内存的管理基本单位和虚拟内存页一样大的话,浪费的内存就会变多(因为内存申请是以页为单位的,不管你用多少,都会占用1页)。2MB的页对于x86-64系统来说也还是太大了,如果要实现则必须用几个小页组成大页来实现。如果小页是4kB,那么就意味着要在物理内存中连续地分配512个小页。要做到这个比较困难,而且系统运行一段时间物理内存就会变得碎片化。Linux系统在操作系统启动时遇险分配了一块内存区域存放大页(hugetlbs文件系统),固定数量的物理页被保留给虚拟大页使用。所以大页适合在以下场景:性能优先、资源充足、不怕配置繁琐,比如数据库应用。提高虚拟页最小尺寸(前面讲的大页是可选的)也会产生问题:内存影射操作(比如加载应用程序)必须能够适配页尺寸。比页尺寸更小的映射是不允许的。一个可执行程序的各个部分,在大多数架构中,的关系是固定的。如果页尺寸变得太大,以至于超出了可执行程序所适配的大小,那么就无法加载了。看下图:$ eu-readelf -l /bin/lsProgram Headers: Type Offset VirtAddr PhysAddr FileSiz MemSiz Flg Align… LOAD 0x000000 0x0000000000400000 0x0000000000400000 0x0132ac 0x0132ac R E 0x200000 LOAD 0x0132b0 0x00000000006132b0 0x00000000006132b0 0x001a71 0x001a71 RW 0x200000…Figure 4.3: ELF Program Header Indicating Alignment Requirements这是一个x86-64可执行二进制的头,里面规定了内存对齐单位是0x200000 = 2,097,152 = 2MB,如果页尺寸比这个大就不行了。另一个使用大页的影响是减少page table tree的层级,因为offset变大了,那么剩下的留给页表的部分就变少了,那么page tree walking就更快了,那么TLB missing所要产生的工作就变少了。下面这段没有看懂:Beyond using large page sizes, it is possible to reduce the number of TLB entries needed by moving data which is used at the same time to fewer pages. This is similar to some optimizations for cache use we talked about above. Only now the alignment required is large. Given that the number of TLB entries is quite small this can be an important optimization.4.4 Impact Of Virtualization大致意思是现代虚拟化技术能够消解大部分因虚拟化导致的TLB性能损失,但是这个开销不会完全消失。 ...

March 20, 2019 · 2 min · jiezi

(笔记)CPU & Memory, Part 1: RAM

博文:https://chanjarster.github.io…原文:What every programmer should know about memory, Part 1, RAM1 Introduction如今的计算机架构中CPU和main memory的访问速度的差异是很大的,解决这一瓶颈有这么几种形式:RAM硬件设计的改善(速度和并行)Memory controller设计CPU caches给设备用的Direct memory access(DMA)2 Commodity Hardware Today大众架构Figure 2.1: Structure with Northbridge and Southbridge所有CPU通过FSB连接到北桥,北桥包含内存控制器(memory controller),连接到RAM。不同的内存类型如SRAM、DRAM有不同的内存控制器。南桥又称I/O桥,如果要访问其他系统设备,北桥必须和南桥通信。南桥连接着各种不同的bus这个架构要注意:CPU之间的所有数据通信必须经过FSB,而这个FSB也是CPU和北桥通信的bus。所有和RAM的通信都必须经过北桥RAM只有一个端口(port)CPU和挂接到南桥设备的通信则有北桥路由可以发现瓶颈:为设备去访问RAM的瓶颈。解决办法是DMA,让设备直接通过北桥访问RAM,而不需要CPU的介入。如今挂到任一bus的所有高性能设备都能利用DMA。虽然DMA减少了CPU的工作量,但是争用了北桥的带宽北桥到RAM的瓶颈。老的系统里只有一条通往所有RAM芯片的bus。现在的RAM类型要求有两条独立的bus,所以倍增了带宽(DDR2里称为channel)。北桥通过多个channel交替访问内存。多内存控制器比较贵的系统北桥自己不包含内存控制器,而是外接内存控制器:Figure 2.2: Northbridge with External Controllers在这种架构里有多个内存bus,大大增加了带宽。在并发内存访问的时候,可以同时访问不同的memory bank(我理解为就是内存条)。而这个架构的瓶颈则是北桥内部的带宽。NUMA除了使用多个内存控制器,还可以采用下面的架构增加内存带宽。做法就是把内存控制器内置在CPU里。每个CPU访问自己的本地RAM。Figure 2.3: Integrated Memory Controller这个架构同样也有缺点:因为这种系统里的所有CPU还是要能够访问所有的RAM,所以the memory is not uniform anymore (hence the name NUMA - Non-Uniform Memory Architecture - for such an architecture)。访问本地内存速度是正常的,访问别的CPU的内存就不一样了,CPU之间必须interconnect才行。在上图中CPU1访问CPU4的时候就要用到两条interconnect。2.1 RAM Types2.1.1 Static RAM访问SRAM没有延迟,但SRAM贵,容量小。Figure 2.4: 6-T Static RAM电路图就不解释了。2.2.1 Dynamic RAMFigure 2.5: 1-T Dynamic RAM电路图就不解释了。DRAM物理结构:若干RAM chip,RAM chip下有若干RAM cell,每个RAM cell的状态代表1 bit。访问DRAM有延迟(等待电容充放电),但DRAM便宜,容量大。商业机器普遍使用DRAM,DDR之类的就是DRAM。2.1.3 DRAM AccessFigure 2.7: Dynamic RAM Schematic访问DRAM的步骤:RAS(Row address selection)CAS(Column address selection)传输数据RAS和CAS都需要消耗时钟频率,如果每次都需要重新RAS-CAS则性能会低。如果一次性把一行的数据都传输,则速度很快。2.1.4 Conclusions不是所有内存都是SRAM是有原因的(成本原因)memory cell必须被单独选择才能够使用address line的数目直接影响到内存控制器、主板、DRAM module、DRAM chip的成本需要等待一段时间才能得到读、写操作的结果2.2 DRAM Access Technical Details略。2.2.4 Memory Types现代DRAM内置I/O buffer增加每次传输的数据量。Figure 2.14: DDR3 SDRAM Operation2.2.5 Conclusions假如DRAM的时钟频率为200MHz,I/O buffer每次传送4份数据(商业宣传其FSB为800MHz),你的CPU是2GHz,那么两者时钟频率则是1:10,意味着内存延迟1个时钟频率,那么CPU就要等待10个时钟频率。2.3 Other Main Memory Users网络控制器、大存储控制器,使用DMA访问内存。PCI-E卡也能通过南桥-北桥访问内存。USB也用到FSB。高DMA流量会占用FSB,导致CPU访问内存的时候等待时间变长。在NUMA架构中,可以CPU使用的内存不被DMA影响。在Section 6会详细讨论。没有独立显存的系统(会使用内存作为显寸),这种系统对于RAM的访问会很频繁,造成占用FSB带宽,影响系统性能。 ...

March 18, 2019 · 1 min · jiezi

(笔记)CPU & Memory, Part 2: CPU caches

博文:https://chanjarster.github.io…原文:What every programmer should know about memory, Part 2: CPU caches关键词:Cache prefetching、TLB cache missing、MESI protocol、Cache types(L1d、L1i、L2、L3)3.1 CPU Caches in the Big Picture内存很慢,这就是为何CPU cache存在的原因,CPU cache内置在CPU内部,SRAM。CPU cache尺寸不大。CPU cache处于CPU和内存之间,默认情况下CPU所读写的数据都存在cache中。Intel将CPU cache分为data cache和code cache,这样会有性能提升。随着CPU cache和内存的速度差异增大,在两者之间增加了更大但是更慢的CPU cache,为何不扩大原CPU cache的尺寸?答案是不经济。现代CPU core拥有三级缓存。L1d是data cache,L1i是instruction cache(code cache)。上图只是概要,现实中从CPU core到内存的数据流一路上可以通过、也可以不通过各个高层cache,这取决于CPU的设计者,这部分对于程序员是不可见的。每个处理器拥有多个core,每个core几乎拥有所有硬件资源的copy,每个core可以独立运行,除非它们用到了相同的资源。每个core有用多个thread,每个thread共享其所属处理器的所有资源,Intel的thread仅对reigster做分离,甚至这个也是有限制的,有些register依然是共享的。上面这张图:两个处理器,processors,大的灰色矩形每个处理器有一个L3 cache和L2 cache(从上往下看第一个深绿色L3 cache,第二个较浅绿色L2 cache)每个处理器有两个core(小的灰色矩形)每个core有一个L1d cache和L1i cache(两个浅绿色矩形)每个core有两个thread,红色矩形,同一个processor的所有core都共享相同的L2/L3 cache3.2 Cache Operation at High Level插播概念word:Word,数据的自然单位,CPU指令集所能处理的数据单位。在x86-64架构中,word size=64 bits=8 bytes。CPU cache中存储的条目(entry)不是word,而是cache line,如今一条cache line大小为64 bytes。每次从RAM中抓取数据的时候不仅会将目标数据抓过来,还会将其附近的数据一并抓过来,构成64 bytes大小的cache line。当一个cache line被修改了,但是还没有被写到内存(main memory),则这个cache line被标记为dirty。一旦被写到内存,则dirty标记被清除。对于多处理器系统,处理器之间会互相监视写动作,并维持以下规则:A dirty cache line is not present in any other processor’s cache.Clean copies of the same cache line can reside in arbitrarily many caches.Cache eviction类型:exclusive,当要加载新数据的时候,如果L1d已满,则需要将cache line推到L2,L2转而推到L3,最终推到main memory。优点:加载新数据的时候只需要碰L1d。缺点:eviction发生时代价逐级增高。inclusive,L1d中的所有cache line同样存在于L2中。优点:L1d eviction快,因为只需要碰L2。缺点:浪费了一些L2的空间。下表是Intel奔腾M处理访问不同组件所需的CPU周期:To WhereCyclesRegister<= 1L1d3L214Main Memory~240下图是写不同尺寸数据下的性能表现:根据经验可以推测出L1d size=2^12=4K,L2 size=2^20=1M。当数据<=4K时,正好能够放进L1d中,操作的CPU周期<10个。当数据>4K and <=1M时,会利用到L2,操作的CPU周期<75。当数据>1M时,CPU操作周期>400,则说明没有L3,此时是直接访问内存了。非常重要:下面的例子里CPU访问数据是按照以下逻辑:CPU只能从L1d cache访问数据如果L1d没有数据,则得先从L2把数据加载到L1d如果L2没有数据,则得先从main memory(RAM)加载数据也就是说如果这个数据一开始在L1d、L2都不存在,那么就得先从main memory加载到L2,然后从L2加载到L1d,最后CPU才可以访问。3.3 CPU Cache Implementation Details3.3.1 Associativity没看懂。略。3.3.2 Measurements of Cache Effectskeyword:cache prefetching、TLB cache miss测试方法是顺序读一个l的数组:struct l { struct l *n; long int pad[NPAD];};根据NPAD不同,元素的大小也不同:NPAD=0,element size=8 bytes,element间隔 0 bytesNAPD=7,element size=64 bytes,element间隔 56 bytesNPAD=15,element size=128 bytes,element间隔 120 bytesNPAD=31,element size=256 bytes,element间隔 248 bytes被测CPU L1d cache=16K、L2 cache=1M、cache line=64 bytes。Single Threaded Sequential Accesscase 1: element size=8 bytes下面是NPAD=0(element size=8 bytes,element间隔0 bytes),read 单个 element的平均时钟周期:Figure 3.10: Sequential Read Access, NPAD=0上面的Working set size是指数组的总尺寸(bytes)。可以看到就算数据尺寸超过了16K(L1d size),对每个元素的读的CPU周期也没有到达14,甚至当数据尺寸超过1M(L2 size)时也是这样,这就是因为cache prefetching的功劳:当你在读L1d cache line的时候,处理器预先从L2 cache抓取到L1d cache,当你读L1d next cache line的时候这条cache line已经准备好了。而L2也会做prefetching动作Cache prefetching是一项重要的优化手段,在使用连续的内存区域的时候,处理器会将后续数据预先加载到cache line中,也就是说当在访问当前cache line的时候,下一个cache line的数据已经在半路上了。Prefetching发生既会发生在L1中也会发生在L2中。case 2: element size >= cache line, 总尺寸 <= L2各个尺寸element的情况:NPAD=7(element size=64 bytes,element间隔56 bytes)NPAD=15,element size=128 bytes,element间隔 120 bytesNPAD=31,element size=256 bytes,element间隔 248 bytesFigure 3.11: Sequential Read for Several Sizes观察working set size <= L2,看(210~219区段):当working set size <= L1d的时候,时钟周期和NPAD=0持平。当working set size > L1d <= L2的时候,时钟周期和L2本身的周期吻合,在28左右这是为什么呢?此时prefetching没有起到作用了吗?这是因为:prefetching本身需要时钟周期顺序read array实际上就是在顺序read cache line当NPAD=0时,element size=8,就意味着你要read多次才会用光cache line,那么prefetching可以穿插在read之间进行,将next cache line准备好当NPAD=7,15,31时,element size>=cache line,那么就意味着每次read都把一条cache line用完,没有留给prefetching的时钟周期了,下一次read的时候就只能老实从L2加载,所以时钟周期在28左右。case 3: selement size >= cache line, 总尺寸 > L2还是看各个尺寸element的情况:NPAD=7(element size=64 bytes,element间隔56 bytes)NPAD=15,element size=128 bytes,element间隔 120 bytesNPAD=31,element size=256 bytes,element间隔 248 bytesFigure 3.11: Sequential Read for Several Sizes观察working set size > L2,看(219之后的区段):NPAD=7(element size=64),依然有prefetching的迹象NPAD=15(element size=128)、NPAD=31(element size=256)则没有prefetching的迹象这是因为处理器从size of the strides判断NPAD=15和31,小于prefetching window(具体后面会讲),因此没有启用prefetching。而元素大小妨碍prefetching的硬件上的原因是:prefetching无法跨过page boundaries。而NPAD=15与31的差别很大则是因为TLB cache miss。测量TLB效果TLB是用来存放virtual memory address到physical memory address的计算结果的(虚拟内存地址和物理内存地址在后面会讲)。测试NPAD=7(element size=64),每个元素按照下列两种方式排列的性能表现:On cache line,数组中的每个元素连续。也就是每次迭代需要新的cache line,每64个元素需要一个新page。On page,数组中的每个元素在独立的Page中。每次迭代需要新的cache line。Figure 3.12: TLB Influence for Sequential Read蓝色曲线看到,当数据量超过212 bytes(4K),曲线开始飙升。因此可以推测TLB的大小为4K。因为每个元素大小为64 bytes,因此可以推测TLB的entry数目为64.从虚拟内存地址计算物理内存地址,并将结果放到TLB cache中是很耗时的。main memory读取或从L2读取数据到cache line之前,必须先计算物理内存地址。可以看到越大的NPAD就越会降低TLB cache的效率。换句话说,越大的元素尺寸就越会降低TLB cache的效率。所以address translation(地址翻译)的惩罚会叠加到内存访问上,所以Figure 3.11的NPAD=31(element size=256)周期数会比其他更高,而且也比理论上访问RAM的周期高。case 4: Sequential Read and Write, NPAD=1测试NPAD=1,element size=16 bytes,顺序读与写:Follow,和之前一样是顺序读的测试结果,作为baselineInc,每次迭代对pad[0]++Addnext0,每次迭代读取下一个元素的pad[0],把值加到自己的pad[0]上Figure 3.13: Sequential Read and Write, NPAD=1按照常理来说,Addnext0应该比较慢因为它做的工组比较多,然而在某些working set size下反而比Inc要好,这是因为:Addnext0的读取下一个元素的pad[0]这个动作实际上是force prefetch。当程序读取下一个元素的pad[0]的时候,数据已经存在于cache line之中了。所以只要working set size符合能够放到L2中,Addnext0的表现和Follow一样好下面这段没看懂,也许这不重要。The “Addnext0” test runs out of L2 faster than the “Inc” test, though. It needs more data loaded from main memory. This is why the “Addnext0” test reaches the 28 cycles level for a working set size of 221 bytes. The 28 cycles level is twice as high as the 14 cycles level the “Follow” test reaches. This is easy to explain, too. Since the other two tests modify memory an L2 cache eviction to make room for new cache lines cannot simply discard the data. Instead it has to be written to memory. This means the available bandwidth on the FSB is cut in half, hence doubling the time it takes to transfer the data from main memory to L2.case 4: Sequential Read on larger L2/L3测试NPAD=15,element size=128 bytes。Figure 3.14: Advantage of Larger L2/L3 Caches最后一级cache越大,则曲线逗留于L2访问开销对应的低等级的时间越长第二个处理器在220时比第一个处理快一倍,是因为它的L3第三个处理器表现的更好则是因为它的4M L2所以缓存越大越能得到性能上的提升。Single Threaded Random Access Measurements之前已经看到处理器通过prefetching cache line到L2和L1d的方法,可以隐藏main memory的访问开销,甚至L2的访问开销。但是,只有在内存访问可预测的情况下,这才能工作良好。下图是顺序访问和随机访问的对比:Figure 3.15: Sequential vs Random Read, NPAD=0后面的没有看懂。3.3.3 Write behaviorcache应该是coherent的,cache的coherency对于userlevel 代码应该是完全透明的,内核代码除外。如果一个cache line被修改了,那么自此时间点之后的系统的结果和压根没有cache并且main memory被修改的结果是一样。有两个实现策略:Write-through:一旦cache line被写,则马上将cache line写到main memory总是保证main memory和cache保持一致,无论什么时候cache line被替换,所cache的内容可以随时丢弃优点:实现起来最简单缺点:虽然简单但是不快。如果一个程序不停的修改一个本地变量,会占用FSB带宽。Write-back:cache line被写不马上写到main memory,仅标记为dirty。当cache line在之后的某个时间点被drop,dirty标记会指导处理器把内容写到main memory绝大多数系统采用的是这个策略处理器甚至可以在驱散cache line之前,利用FSB的空闲空间存储cache line的内容。这样一来就允许清除dirty标记,当需要新空间的时候,处理器就能够直接丢弃cache line。还有另外两个策略,它们都用于地址空间的特殊区域,这些区域不由实际的RAM支持。Write-combining:Write-combining是一种首先的cache优化策略,更多的用于设备的RAM上,比如显卡。传输数据到设备的开销比访问本地RAM要大得多,所以要尽可能避免传输次数太多。如果仅因为cache line的一个word的修改而要把整个cache line都传输太浪费。因此,write-combining把多个写访问合并在一起,然后再把cache line写出去。这可以加速访问设备RAM的速度。个人插播:这个策略牺牲了一定的latency,但是提高了throughput,类似于批处理。Uncacheable:内存地址压根就不存在RAM里,这些地址一般都是硬编码的。在商业机器上,一般来说这些地址会被翻译成访问card和连接到某个总线的设备(PCIe)。这些内存不应该被缓存。3.3.4 Multi-processor support在多处理器系统和多核处理器中,对于所有不共享的cache,都会存在cache内容不一致的问题。两个处理器之间不会共享L1d、L1i、L2、L3,同一处理器的两个核之间至少不会共享L1d。提供一条能从A处理器直接访问B处理器的cache的通道是不切实际的,因为速度不够快。所以比较实际的做法是将cache内容传输到其他处理器以备不时之需。对于多核处理器也采用这种做法。那什么时候传输呢?当一个处理器需要一条cache line做读/写,但是它在其他处理器里是dirty时。那么一个处理器是如何决定一条cache line在另外一个处理器里是否dirty呢?通常来说内存访问是读,而读不会把一个cache line变成dirty。处理器每次对cache line的写访问之后都把cache line信息广播出去是不切实际的。MESI cache coherency protocol开发了MESI缓存协同协议(MESI cache coherency protocol),规定了一条cache line的状态有四种:Modified、Exclusive、Shared、Invalid。Modified: 本地处理器刚修改cache line,也意味着它是所有cache中的唯一copy。Exclusive: cache line没有被修改,但已知没有被加载到任何其他处理的cache中。Shared: cache line没有被修改,可能存在于其他处理器的cache中。Invalid: cache line无效,比如还未被使用。MESI所解决的问题和分布式缓存中数据同步的问题是一致的,好好看看,这能够带来一些启发使用这四个状态有可能有效率地实现write-back策略,同时支持多处理器并发使用read-only数据。Figure 3.18: MESI Protocol Transitions下面是四种状态变化的解读:Invalid:最开始所有的cache line都是空的,也即Invalid如果数据加载到cache line的目的是为了写,变成Modified如果数据加载到cache line的目的是为了读,若其他处理器是否也加载了相同的cache line,变成Shared如果没有,变成ExclusiveModified如果一条Modified cache line被本地处理器读or写,状态不变。如果B处理器要读A处理器的Modified cache line,则A处理器必须将内容发送给B处理器,然后状态变成Shared。发送给B处理器的数据同样也被memory controller接收和处理,然后存到main memory里。如果这一步没有做,那么状态就不能变成Shared。如果B处理器要写A处理器的Modified cache line,则A处理器要把数据传给B,然后标记为Invalid。这就是臭名昭著的Request For Ownership(RFO)(通过address bus)。在最后一级缓存执行这个操作和I->M一样,代价相对来说是比较高的对于write-through cache,则必须加上在高一级cache写新的cache line,或者写到main memory的时间,进一步增加了代价Shared如果本地处理器读一条Shared cache line,状态不变。如果本地写一条Shared cache line,则变成Modified。所有其他处理器的cache line copy变成Invalid。所以写操作必须通过RFO广播到其他处理器。如果B处理器要读A处理器的Shared cache line,状态不变。如果B处理器要写A处理器的Shared cache line,则变成Invalid,不牵涉到bus operation。ExclusiveExlusive和Shared一样除了一个区别:本地写不需要RFO。所以处理器会尽可能把多的cache line维持在Exclusive状态,而不是Shared状态。当信息不足的时候,Shared状态作为一种fallback——原文没有说明白是什么信息,猜测应该是当无法知道其他处理器是否拥有相同cache line的时候,就把它设为Shared,这样做会比较安全。E->M 比 S->M 快得多所以在多处理器系统中,除了填充cache line之外,我们还得关注RFO消息对性能的影响。只要出现了RFO消息,就会变慢。有两种场景RFO消息是必须的:一个线程从一个处理器迁移到另一个处理器,所有的cache line都必须一次性移动到新处理器同一条cache line是真的被两个处理器需要。在稍小一点的尺度上,多核处理器内部就存在这样的情况,只是代价小一点而已,RFO可能会被发送很多次。影响Coherency protocol速度的因素:RFO出现的频率,应该越低越好一次MESI状态变化必须等所有处理器都应答之后才能成功,所以最长的可能应答时间决定了coherency protocol的速度。FSB是共享资源,大多数系统所有处理器通过同一条bus连到memory controller。如果一个处理器饱和了FSB,则共享同一bus的两个或四个处理器将进一步限制每个处理器可用的带宽。就算每个处理器有自己的bus连接memory controller(Figure 2.2),那么处理器到memory module的bus还是会被共享的。在多线程/多进程程序里,总有一些synchronization的需求,这些synchronization则是使用memory实现的,所以就会有一些RFO消息。所以concurrency严重地受限于可供synchronization的有限带宽。程序应该最小化从不同处理器、不同核访问同一块内存区域的操作。Multi Threaded Measurements用之前相同的程序测试多线程的表现,采用用例中最快的线程的数据。所有的处理器共享同一个bus到memory controller,并且只有一条到memory modules的bus。case 1: Sequential Read Access, Multiple ThreadsFigure 3.19: Sequential Read Access, Multiple Threads这个测试里没有修改数据,所有cache line都是shared,没有发生RFO。但是即便如此2线程的时候有18%的性能损失,在4线程的时候则是34%。那么可能的原因就只可能是一个或两个瓶颈所造成的:处理器到memory controller的bus、memory controller到memory modules的bus。一旦working set超过L3尺寸之后,就要从main memory prefetch数据了,带宽就不够用了case 2: Sequential Increment, Multiple Threads这个测试用的是 “Sequential Read and Write, NPAD=1,Inc”,会修改内存。Figure 3.20: Sequential Increment, Multiple Threads注意图中的Y轴不是线性增加的,所以看上去很小的差异实际上差别很大。2线程依然有18%的性能损失,而4线程则有93%的性能损失,这意味4线程的时候prefetch流量核write-back流量饱和了bus。图中也可以发现只要有多个线程,L1d基本上就很低效了。L2倒不像L1d,似乎没有什么影响。这个测试修改了内存,我们预期会有很多RFO消息,但是并没有看见2、4线程相比单线程有什么性能损失。这是因为测试程序的关系。case 3: Random Addnextlast, Multiple Threads下面这张图主要是为了展现令人吃惊的高数字,在极端情况下处理list中的单个元素居然要花费1500个周期。Figure 3.21: Random Addnextlast, Multiple Threads总结case 1、2、3把case 1、2、3中的最大working set size的值总结出多线程效率:#ThreadsSeq ReadSeq IncRand Add21.691.691.5442.982.071.65Table 3.3: Efficiency for Multiple Threads这个表显示了在最大working set size,使用多线程能获得的可能的最好的加速。理论上2线程应该加速2,4线程应该加速4。好好观察这个表里的数字和理论值的差异。下面这张图显示了Rand Add测试,在不同working set size下,多线程的加速效果:Figure 3.22: Speed-Up Through ParallelismL1d尺寸的测试结果,在L2和L3范围内,加速效果基本上是线性的,一旦当尺寸超过L3时,数字开始下坠,并且2线程和4线程的数字下坠到同一点上。这也就是为什么很难看到大于4个处理器的系统使用同一个memory controller,这些系统必须采用不同的构造。不同情况下上图的数字是不一样的,这个取决于程序到底是怎么写的。在某些情况下,就算working set size能套进最后一级cache,也无法获得线性加速。但是另一方面依然有可能在大于两个线程、更大working set size的情况下获得线性加速。这个需要程序员做一些考量,后面会讲。special case: hyper-threadsHyper-Threads(有时候也被称为Symmetric Multi-Threading, SMT),是CPU实现的一项技术,同时也是一个特殊情况,因为各个线程并不能够真正的同时运行。超线程共享了CPU的所有资源除了register。各个core和CPU依然是并行运行的,但是hyper-threads不是。CPU负责hyper-threads的分时复用(time-multiplexing),当当前运行的hyper-thread发生延迟的时候,就调度令一个hyper-thread运行,而发生延迟的原因大部分都是因内存访问导致的。当程序的运行2线程在一个hyper-thread核的时候,只有在以下情况才会比单线程更有效率:2个线程的运行时间之和低于单线程版本的运行时间。这是因为当一个线程在等待内存的时候可以安排另一个线程工作,而原本这个是串形的。一个程序的运行时间大致可以用下面这个简单模型+单级cache来计算:Texe = N[(1-Fmem)Tproc + Fmem(GhitTcache + (1-Ghit)Tmiss)]N = Number of instructions. 指令数Fmem = Fraction of N that access memory. N的几分之几访问内存Ghit = Fraction of loads that hit the cache. 加载次数的几分之几命中cacheTproc = Number of cycles per instruction. 每条指令的周期数Tcache = Number of cycles for cache hit. 命中cache的周期数Tmiss = Number of cycles for cache miss. 没命中cache的周期数Texe = Execution time for program. 程序的执行时间为了使用两个线程有意义,两个线程中每个线程的执行时间必须至多是单线程代码的一半。如果把单线程和双线程放到等式的两遍,那么唯一的变量就是cache命中率。不使线程执行速度降低50%或更多(降低超过50%就比单线程慢了),然后计算所需的最小cache命中率,得到下面这张图:Figure 3.23: Minimum Cache Hit Rate For Speed-UpX轴代表了单线程代码的Ghit,Y代表了双线程代码所需的Ghit,双线程的值永远不能比单线程高,否则的话就意味着单线程可以用同样的方法改进代码了。单线程Ghit < 55%的时候,程序总是能够从多线程得到好处。绿色代表的是目标区域,如果一个线程的降速低于50%且每个线程的工作量减半,那么运行时间是有可能低于单线程的运行时间的。看上图,单线程Ghit=60%时,如果要得到好处,双线程程序必须在10%以上。如果单线程Ghit=95%,多线程则必须在80%以上,这就难了。特别地,这个问题是关于hyper-threads本身的,实际上给每个hyper-thread的cache尺寸是减半的(L1d、L2、L3都是)。两个hyper-thread使用相同的cache来加载数据。如果两个线程的工作集不重叠,那么原95%也可能减半,那么就远低于要求的80%。所以Hyper-threads只在有限范围的场景下有用。单线程下的cache命中率必须足够低,而且就算减半cache大小新的cache命中率在等式中依然能够达到目标。也只有这样使用Hyper-thread才有意义。在实践中是否能够更快取决于处理器是否能够充分交叠一个线程的等待和另一个线程的执行。而代码为了并行处理所引入的其他开销也是要考虑进去的。所以很明白的就是,如果两个hyper-threads运行的是两个完全不同的代码,那么肯定不会带来什么好处的,除非cache足够大到能够抵消因cache减半导致的cache miss率的提高。除非操作系统的工作负载由一堆在设计上真正能够从hyper-thread获益的进程组成,可能还是在BIOS里关掉hyper-thread比较好。3.3.5 Other Details现代处理器提供给进程的虚拟地址空间(virtual address space),也就是说有两种地址:虚拟的和物理的。虚拟地址不是唯一的:一个虚拟地址在不同时间可以指向不同的物理地址。不同进程的相同的地址也可能指向不同的物理地址。处理器使用虚拟地址,虚拟地址必须在Memory Management Unit(MMU)的帮助下才能翻译成物理地址。不过这个步骤是很耗时的(注:前面提到的TLB cache缓存的是虚拟->物理地址的翻译结果)。现代处理器被设计成为L1d、L1i使用虚拟地址,更高层的cache则使用物理地址。通常来说不必关心cache地址处理的细节,因为这些是不能改变的。Overflowing the cache capacity是一件坏事情;如果大多数使用的cache line都属于同一个set,则所有缓存都会提前遇到问题。第二个问题可以通过virtual address来解决,但是无法避免user-level进程使用物理地址来缓存。唯一需要记住的事情是,要尽一切可能,不要在同一个进程里把相同的物理地址位置映射到两个或更多虚拟地址。Cache replacement策略,目前使用的是LRU策略。关于cache replacement程序员没有什么事情可做。程序员能做的事情是:完全使用逻辑内存页(logical memory pages)使用尽可能大的页面大小来尽可能多样化物理地址3.4 Instruction Cache指令cache比数据cache问题更少,原因是:被执行的代码量取决于所需代码的大小,而代码大小通常取决于问题复杂度,而问题复杂度是固定的。程序的指令是由编译器生成的,编译器知道怎么生成好代码。程序流程比数据访问内存更容易预测,现代处理器非常擅长预测模式,这有助于prefetching代码总是具有良好的空间、时间局部性。CPU核心和cache(甚至第一级cache)的速度差异在增加。CPU已被流水线化,所谓流水线指一条指令的执行是分阶段的。首先指令被解码,参数被准备,最后被执行。有时候这个pipeline会很长,长pipeline就意味着如果pipeline停止(比如一条指令的执行被中断了),那它得花一些时间才能重新找回速度。pipeline停止总是会发生的,比如无法正确预测下一条指令,或者花太长时间加载下一条指令(比如从内存里加载)。现代CPU设计师花费了大量时间和芯片资产在分支预测上,为了尽可能不频繁的发生pipeline停止。3.4.1 Self Modifying Code早些时候为了降低内存使用(内存那个时候很贵),人们使用一种叫做Self Modifing Code(SMC)的技术来减少程序数据的尺寸。不过现在应该避免SMC,因为如果处理器加载一条指令到流水线中,而这条指令在却又被修改了,那么整个工作就要从头来过。所以现在到处理器假设code pages是不可变的,所以L1i没有使用MESI,而是使用更简单的SI3.5 Cache Miss Factors我们已经看到内存访问cache miss导致的开销极具增大,但是有时候这个问题是无法避免的,所以理解实际的开销以及如何缓解这个问题是很重要的。3.5.1 Cache and Memory Bandwidth测试程序使用x86和x86-64处理器的SSE指令每次加载16 bytes,working set size从1K到512M,测试的是每个周期能够加载/存储多少个bytes。Figure 3.24: Pentium 4 Bandwidth这个图是64-bit Intel Netburst处理器的测试结果。先看读的测试可以看到:当working set size在L1d以内的时候,16 bytes/cycle当L1d不够用的时候,性能下降很快,6 bytes/cycle到218的时候又下降了一小段是因为DTLB耗尽了,意思是对每个新page需要额外工作。这个测试是顺序读的,很利于prefetching,而在任何working set size下都能够达到5.3/cycle,记住这些数字,它们是上限,因为真实程序永远无法达到这个值。在看写和copy的测试:就算是最小的working set size,也不超过4 bytes/cycle。这说明Netburst处理器使用的Write-through策略,L1d的速度显然受制于L2的速度。copy测试是把一块内存区域copy到另一个不重叠的内存区域,因为上述原因它的结果也没有显著变差。当L2不够用的时候,下降到0.5 bytes/cycle。下面是两个线程分别钉在同一核心的两个Hyper-thread上的测试情况:Figure 3.25: P4 Bandwidth with 2 Hyper-Threads这个结果符合预期,因为Hyper-thread除了不共享register之外,其他资源都是共享的,cache和带宽都被减半了。这意味着就算一个线程在在等待内存的时候可以让另一个线程执行,但是另一个线程实际上也在等待,所以并没有什么区别。下图是Intel Core2处理器的测试结果(对比Figure 3.24):Figure 3.26: Core 2 BandwidthCore 2 L2是P4 L2的四倍。这延迟了write和copy测试的性能下跌。read测试在所有的working set size里都维持在16 bytes/cycle左右,在220处下跌了一点点是因为DTLB放不下working set。能够有这么好的性能表现不仅意味着处理器能够及时地prefetching和传输数据,也意味着数据是被prefetch到L1d的。看write和copy的表现,Core 2处理器的L1d cache没有采用Write-through策略,只有当必要的时候L1d才会evict,所以能够获得和read接近的表现。当L1d不够用的时候,性能开始下降。当L2不够用的时候再下跌很多。下图是Intel Core2处理器双线程跑在两个核心上:Figure 3.27: Core 2 Bandwidth with 2 Threads这个测试两个线程分别跑在两个核心上,两个线程访问相同的内存,没有完美地同步。read性能和Figure 3.26没有什么差别。看write和copy的表现,有意思的是在working set能够放进L1d的表现和直接从main memory读取的表现一样。两个线程访问相同的内存区域,针对cache line的RFO消息肯定会被发出。这里可以看到一个问题,RFO的处理速度没有L2快。当L1d不够用的时候,会将修改的cache line flush到共享的L2,这是性能有显著提升是因为L1d中的数据被flush到L2,那就没有RFO了。而且因为两个核心共享FSB,每个核心只拥有一半的FSB带宽,意味着每个线程的性能大约是单线程的一半。厂商的不同版本CPU和不同厂商的CPU的表现都是不同的。原文里后面比较了AMD Opteron处理器,这里就不写了。3.2.5 Critical Word Load数据是一block为单位从main memory传输到cache line的,一个block大小为64 bits(记得前面说的word也是64 bits),一条cache line的大小是64/128 bytes,所以填满一个cache line要传输8/16次。后面没有看懂,大致意思是Critical word是cache line中的关键word,程序要读到它之后才能继续运行,但是如果critical word不是cache line的第一个,那么就得等前面的word都加载完了之后才行。blah blah blah,不过这个理解可能也是不对的。3.5.3 Cache Placementcache和hyper-thread、core、处理器的关系是程序员无法控制的,但是程序员可以决定线程在哪里执行,所以cache和CPU是如何关联的就显得重要了。后面没仔细看,大致讲了由于不同的CPU架构,决定如何调度线程是比较复杂的。3.5.4 FSB Influence不细讲了,对比了667MHz DDR2和800 MHz DDR2(20%的提升),测试下来性能有18%的提升接近理论值(20%)。Figure 3.32: Influence of FSB Speed所以更快的FSB的确能够带来好处。要注意CPU可能支持更高的FSB,但是主板/北桥可能不支持的情况。 ...

March 18, 2019 · 4 min · jiezi

SAP专家培训之Netweaver ABAP内存管理和内存调优最佳实践

培训者:SAP成都研究院开发人员Jerry Wang1. Understanding Memory Objects in ABAPNote1: DATA itab WITH HEADER LINE for processing individual table rows have short forms that implicitly use the header line as work area. These short forms are allowed only outside of ABAP Objects./SF0A0001/ANONYMOUS4951422. Use Reference to Access Memory ObjectABAP里引用的用法3. ABAP和很多其他语言一样,支持写时拷贝:As internal tables and strings can become very large, the copy operation may be quite time-consuming. On the other hand, the copy operation is only necessary if a modification is made via one of the references, which is NOT always the case.For this reason, ABAP runtime delays the copy operation until a modification is actually made. These object types (internal tables and strings) keep track of the number of references that point to them, which is referred to as “reference counting.”4. 写时拷贝的工作原理5. ABAP的内存管理之内存清理内存释放关键字CLEAR,REFRESH和FREE的区别:6. ABAP引用类型的内存分配7. ABAP字符串(String)类型的内存分配8. ABAP内存垃圾回收器的工作原理9. ABAP内存垃圾回收的实现算法10. ABAP垃圾回收机制的触发时机11. ABAP垃圾回收的手动触发,请谨慎使用12. 更多关于引用类型的变量在ABAP垃圾回收算法中的影响13. ABAP bound memory的含义the sum of the size of the memory object and the bound memory of all DIRECT children that are table bodies or strings with a reference count of one.Upon deleting the memory object, the bound memory is the minimum amount of memory that is released, and the referenced memory is the maximum amount that can be released. Therefore, the amount of memory that is actually released is somewhere between these two values.14. ABAP内存预分配算法(preallocation mechanism)介绍15. ABAP已分配内存和已使用内存的区别For internal tables and strings, ABAP runtime environment uses a preallocation mechanism that automatically reserves some EXTRA storage when the memory object is created to allow for potential growth.It avoids many allocation and deallocation operations. Otherwise, ABAP runtime environment would have to allocate new storage every time the memory object grows.Due to this preallocation mechanism, both internal tables and strings also have “allocated memory” and “used memory” values as memory sizes.Allocated memory is the amount of memory that is set aside for the memory object. Used memory is the current size of the memory object used by the application.For class objects and anonymous data objects, used memory and allocated memory are the same. Because their size is fixed, they don’t require additional space to accommodate growth.16. ABAP程序内存消耗的尺寸计算介绍17. 什么是ABAP内存分配中的SCC - strongly connected component - 强连通组件18. 有用的ABAP内存分析和调优工具,事务码S_MEMORY_INSPECTOR19. 具体例子,您知道下面这段代码,新生成的引用类型的变量,内存是从哪里分配的?堆?不完全对。Logging on to an application server opens a user session. A user session is assigned its own memory area of the SAP memory, in which SPA/GPA parameters can be stored.For every user session , a main session ( external session ) can be opened. Each main session is assigned its own memory area of ABAP memory ( EXPORT — IMPORT ) rdisp/max_alt_modesEach call of an ABAP program creates a new internal session, in which the called program is loaded.20. 什么是ABAP程序的PXA - Program Execution Area21. ABAP程序的用户上下文 - User Context22. 什么是ABAP的工作进程23. ABAP工作进程的内存虚拟地址空间和物理地址空间的映射Roll Area:Memory area with a set (configurable) size that belongs to a work process.For technical reasons, the roll area is always the first memory available to use for a work process. Only afterwards can extended memory be requested.When the context of a work process changes, the data is copied from the roll area to a common resource known as the roll file. To prevent repeated copying, another roll buffer is located in between, which is part of the shared memoryPaging area:Allocation of memory for the current internal session by transferring pages out of memory, similarly to operating system paging. SAP’s memory management concept currently limits SAP Paging to cases where the ABAP commands EXTRACT and EXPORT… TO MEMORY… are used.Private memory:If the extended memory is fully occupied, or the limit for the work process has been exceeded, private memory is assigned to the work process. This is known as private memory because it is specific to the process, and the user context can no longer be processed by a different work process (PRIV mode).ABAP扩展内存 - extended memoryUser context is stored in the extended memory (EM) to enable fast context change . Depending on the operating system, how SAP implements EM addressing/mapping is different.When the context is changed, the user context is not copied as with the roll area. Instead it is allocated to alternating work processes by mapping operations which results in a faster context change because less data is copied and mapping an extended area is not work-intensive. The result is low CPU usage and shorter access times.SAP dispatcher is responsible for the following principle tasks:1. Initialization, reading profile parameters, starting work processes and logging on to the message server2. Evenly distributing the transaction load across work processes3. Connecting to the GUI layer4. Organizing communication processes这道题的答案:The roll area consists of two segments. The first segment, which can be set with the parameter ztta/roll_first, is assigned to the work process first as memory. If this is used up, the work process has more memory assigned to it. The amount of memory is determined by the difference between the ztta/roll_area and ztta/roll_first parameters.正确答案,不能一概而论,有三种情况。情况1:Roll area: if ( roll_current_area + request size <= roll_area )情况2:Extended memory: if ( roll_current_area + request size > roll_area ) AND ( extend memory is not full )情况3:Private memory: if ( roll_current_area + request size > roll_area ) AND ( extend memory is full )要获取更多Jerry的原创文章,请关注公众号"汪子熙": ...

January 15, 2019 · 4 min · jiezi

go 指针和内存分配详解

定义了解指针之前,先讲一下什么是变量。每当我们编写任何程序时,我们都需要在内存中存储一些数据/信息。数据存储在特定地址的存储器中。内存地址看起来像0xAFFFF(这是内存地址的十六进制表示)。现在,要访问数据,我们需要知道存储它的地址。我们可以跟踪存储与程序相关的数据的所有内存地址。但想象一下,记住所有内存地址并使用它们访问数据会有非常困难。这就是为什么引入变量。变量是一种占位符,用于引用计算机的内存地址,可理解为内存地址的标签。什么是指针指针是存储另一个变量的内存地址的变量。所以指针也是一种变量,只不过它是一种特殊变量,它的值存放的是另一个变量的内存地址。在上面的例子中,指针p包含值0x0001,该值是变量的地址a。Go类型占用内存情况unsafe包可以获取变量的内存使用情况Go语言提供以下基本数字类型:无符号整数uint8,uint16,uint32,uint64符号整数int8,int16,int32,int64 实数float32,float64 Predeclared 整数(依赖系统类型,跟系统有关)uint,int,uintptr (指针)32位系统uint=uint32int=int32uintptr为32位的指针64位系统uint=uint64int=int64uintptr为64位的指针示例:package mainimport ( “fmt” “unsafe”)func main() { var uint8Value uint8 var uint16Value uint16 var uint32Value uint32 var uint64Value uint64 var int8Value int8 var int16Value int16 var int32Value int32 var int64Value int64 var float32Value float32 var float64Value float64 fmt.Println(“uint8Value = Size:”, unsafe.Sizeof(uint8Value)) //uint8Value = Size: 1 fmt.Println(“uint16Value = Size:”, unsafe.Sizeof(uint16Value)) //uint16Value = Size: 2 fmt.Println(“uint32Value = Size:”, unsafe.Sizeof(uint32Value)) //uint32Value = Size: 4 fmt.Println(“uint64Value = Size:”, unsafe.Sizeof(uint64Value))// uint64Value = Size: 8 fmt.Println(“int8Value = Size:”, unsafe.Sizeof(int8Value)) //int8Value = Size: 1 fmt.Println(“int16Value = Size:”, unsafe.Sizeof(int16Value))//int16Value = Size: 2 fmt.Println(“int32Value = Size:”, unsafe.Sizeof(int32Value))//int32Value = Size: 4 fmt.Println(“int64Value = Size:”, unsafe.Sizeof(int64Value)) //int64Value = Size: 8 fmt.Println(“float32Value = Size:”, unsafe.Sizeof(float32Value)) //float32Value = Size: 4 fmt.Println(“float64Value = Size:”, unsafe.Sizeof(float64Value))//float64Value = Size: 8}上面的是基本类型,接下来了解下复杂类型,以结构体类型为例type Example struct { BoolValue bool IntValue int16 FloatValue float32}该结构代表复杂类型。它代表7个字节,带有三个不同的数字表示。bool是一个字节,int16是2个字节,float32增加4个字节。但是,在此结构的内存中实际分配了8个字节。所有内存都分配在对齐边界上,以最大限度地减少内存碎片整理。要确定对齐边界Go用于您的体系结构,您可以运行unsafe.Alignof函数。Go为64bit Darwin平台的对齐边界是8个字节。因此,当Go确定结构的内存分配时,它将填充字节以确保最终内存占用量是8的倍数。编译器将确定添加填充的位置。什么是内存对齐呢?内存对齐,也叫边界对齐(boundary alignment),是处理器为了提高处理性能而对存取数据的起始地址所提出的一种要求。编译器为了使我们编写的C程序更有效,就必须最大限度地满足处理器对边界对齐的要求。从处理器的角度来看,需要尽可能减少对内存的访问次数以实现对数据结构进行更加高效的操作。为什么呢?因为尽管处理器包含了缓存,但它在处理数据时还得读取缓存中的数据,读取缓存的次数当然是越少越好!如上图所示,在采用边界对齐的情况下,当处理器需要访问a_变量和b_变量时都只需进行一次存取(图中花括号表示一次存取操作)。若不采用边界对齐,a_变量只要一次处理器操作,而b_变量却至少要进行两次操作。对于b_,处理器还得调用更多指令将其合成一个完整的4字节,这样无疑大大降低了程序效率。以下程序显示Go插入到Example类型struct的内存占用中的填充:package mainimport ( “fmt” “unsafe”)type Example struct { BoolValue bool IntValue int16 FloatValue float32}func main() { example := &Example{ BoolValue: true, IntValue: 10, FloatValue: 3.141592, } exampleNext := &Example{ BoolValue: true, IntValue: 10, FloatValue: 3.141592, } alignmentBoundary := unsafe.Alignof(example) sizeBool := unsafe.Sizeof(example.BoolValue) offsetBool := unsafe.Offsetof(example.BoolValue) sizeInt := unsafe.Sizeof(example.IntValue) offsetInt := unsafe.Offsetof(example.IntValue) sizeFloat := unsafe.Sizeof(example.FloatValue) offsetFloat := unsafe.Offsetof(example.FloatValue) sizeBoolNext := unsafe.Sizeof(exampleNext.BoolValue) offsetBoolNext := unsafe.Offsetof(exampleNext.BoolValue) fmt.Printf(“example Size: %d\n”, unsafe.Sizeof(example)) fmt.Printf(“Alignment Boundary: %d\n”, alignmentBoundary) fmt.Printf(“BoolValue = Size: %d Offset: %d Addr: %v\n”, sizeBool, offsetBool, &example.BoolValue) fmt.Printf(“IntValue = Size: %d Offset: %d Addr: %v\n”, sizeInt, offsetInt, &example.IntValue) fmt.Printf(“FloatValue = Size: %d Offset: %d Addr: %v\n”, sizeFloat, offsetFloat, &example.FloatValue) fmt.Printf(“Next = Size: %d Offset: %d Addr: %v\n”, sizeBoolNext, offsetBoolNext, &exampleNext.BoolValue)}输出:example Size: 8Alignment Boundary: 8BoolValue = Size: 1 Offset: 0 Addr: 0xc00004c080IntValue = Size: 2 Offset: 2 Addr: 0xc00004c082FloatValue = Size: 4 Offset: 4 Addr: 0xc00004c084Next = Size: 1 Offset: 0 Addr: 0xc00004c088类型结构的对齐边界是预期的8个字节。大小值显示将读取和写入该字段的内存量。正如所料,大小与类型信息一致。偏移值显示进入内存占用的字节数,我们将找到该字段的开头。地址是可以找到内存占用内每个字段的开头的地方。我们可以看到Go在BoolValue和IntValue字段之间填充1个字节。偏移值和两个地址之间的差异是2个字节。您还可以看到下一个内存分配是从结构中的最后一个字段开始4个字节。指针的使用声明一个指针使用以下语法声明类型为T的指针var p *int指针的零值是nil。这意味着任何未初始化的指针都将具有该值nil。让我们看一个完整的例子package mainimport “fmt"func main() { var p *int &p=1}注意:当指针没有指向的时候,不能对(*point)进行操作包括读取,否则会报空指针异常。示例:package mainfunc main() { var p *int *p = 1 //panic: runtime error: invalid memory address or nil pointer dereference}解决方法即给该指针分配一个指向,即初始化一个内存,并把该内存地址赋予指针变量示例:import “fmt"func main() { var p *int var m int p = &m *p = 1 fmt.Println(“m=”, m) fmt.Println(“p=”, p)}或还可以使用内置new()函数创建指针。该new()函数将类型作为参数,分配足够的内存以容纳该类型的值,并返回指向它的指针。import “fmt"func main() { var p *int p = new(int) *p = 1 fmt.Println(“p=”, *p)}初始化指针您可以使用另一个变量的内存地址初始化指针。可以使用&运算符检索变量的地址var x = 100var p int = &x注意我们如何使用&带变量的运算符x来获取其地址,然后将地址分配给指针p。就像Golang中的任何其他变量一样,指针变量的类型也由编译器推断。所以你可以省略p上面例子中指针的类型声明,并像这样写var p = &a取消引用指针您可以在指针上使用运算符来访问存储在指针所指向的变量中的值。这被称为解除引用或间接package mainimport “fmt"func main() { var a = 100 var p = &a fmt.Println(“a = “, a) fmt.Println(“p = “, p) fmt.Println("p = “, p)}输出:a = 100p = 0xc00004c080p = 100您不仅可以使用运算符访问指向变量的值,还可以更改它。以下示例a通过指针设置存储在变量中的值ppackage mainimport “fmt"func main() { var a = 1000 var p = &a fmt.Println(“a (before) = “, a) // Changing the value stored in the pointed variable through the pointer *p = 2000 fmt.Println(“a (after) = “, a)}输出:a (before) = 1000a (after) = 2000指针指向指针指针可以指向任何类型的变量。它也可以指向另一个指针。以下示例显示如何创建指向另一个指针的指针package mainimport “fmt"func main() { var a = 7.98 var p = &a var pp = &p fmt.Println(“a = “, a) fmt.Println(“address of a = “, &a) fmt.Println(“p = “, p) fmt.Println(“address of p = “, &p) fmt.Println(“pp = “, pp) // Dereferencing a pointer to pointer fmt.Println("*pp = “, *pp) fmt.Println(”**pp = “, **pp)}Go中没有指针算术如果您使用过C / C ++,那么您必须意识到这些语言支持指针算法。例如,您可以递增/递减指针以移动到下一个/上一个内存地址。您可以向/从指针添加或减去整数值。您也可以使用关系运算符比较两个三分球==,<,>等。但Go不支持对指针进行此类算术运算。任何此类操作都将导致编译时错误package mainfunc main() { var x = 67 var p = &x var p1 = p + 1 // Compiler Error: invalid operation}但是,您可以使用==运算符比较相同类型的两个指针的相等性。package mainimport “fmt"func main() { var a = 75 var p1 = &a var p2 = &a if p1 == p2 { fmt.Println(“Both pointers p1 and p2 point to the same variable.”) }}Go中传递简单类型import “fmt"func main() { p := 5 change(&p) fmt.Println(“p=”, p)//p= 0}func change(p *int) { *p = 0}Go中所有的都是按值传递,对于复杂类型,传的是指针的拷贝package mainimport “fmt"func main() { var m map[string]int m = map[string]int{“one”: 1, “two”: 2} n := m fmt.Printf("%p\n”, &m) //0xc000074018 fmt.Printf("%p\n”, &n) //0xc000074020 fmt.Println(m) // map[two:2 one:1] fmt.Println(n) //map[one:1 two:2] changeMap(m) fmt.Printf("%p\n”, &m) //0xc000074018 fmt.Printf("%p\n”, &n) //0xc000074020 fmt.Println(m) //map[one:1 two:2 three:3] fmt.Println(n) //map[one:1 two:2 three:3]}func changeMap(m map[string]int) { m[“three”] = 3 fmt.Printf(“changeMap func %p\n”, m) //changeMap func 0xc000060240}直接传指针 也是传指针的拷贝package mainimport “fmt"func main() { var m map[string]int m = map[string]int{“one”: 1, “two”: 2} n := m fmt.Printf("%p\n”, &m) //0xc000074018 fmt.Printf("%p\n”, &n) //0xc000074020 fmt.Println(m) // map[two:2 one:1] fmt.Println(n) //map[one:1 two:2] changeMap(&m) fmt.Printf("%p\n”, &m) //0xc000074018 fmt.Printf("%p\n”, &n) //0xc000074020 fmt.Println(m) //map[one:1 two:2 three:3] fmt.Println(n) //map[two:2 three:3 one:1]}func changeMap(m *map[string]int) { //m[“three”] = 3 //这种方式会报错 invalid operation: m[“three”] (type *map[string]int does not support indexing) (*m)[“three”] = 3 //正确 fmt.Printf(“changeMap func %p\n”, m) //changeMap func 0x0}总结:Go 不能进行指针运算。指针传递是很廉价的,只占用 4 个或 8 个字节。当程序在工作中需要占用大量的内存,或很多变量,或者两者都有,使用指针会减少内存占用和提高效率。指针也是一种类型,不同于一般类型,指针的值是地址,这个地址指向其他的内存,通过指针可以读取其所指向的地址所存储的值。函数方法的接受者,也可以是指针变量。简单类型和复杂类型在传递的时候不同,复杂类型传值或传指针都是指针拷贝。只声明未赋值的变量,golang都会自动为其初始化为零值,基础数据类型的零值比较简单,引用类型和指针的零值都为nil,nil类型不能直接赋值,因此需要通过new开辟一个内存,或指向一个变量。参考资料http://golang.org/doc/faq#Pointershttps://www.callicoder.com/go…https://www.ardanlabs.com/blo...https://www.ardanlabs.com/blo...links目录 ...

December 21, 2018 · 4 min · jiezi