如何处理返回结构的不可变性?

2024-03-28

我正在编写一个具有巨大的二维“单元”数组的游戏。一个单元仅占用 3 个字节。我还有一个名为 CellMap 的类,其中包含作为私有字段的 2D 数组,并提供通过公共索引器对其进行访问。

分析表明,性能问题是由过多 Cell 对象的垃圾回收引起的。所以我决定让 Cell 成为一个结构(它是一个类)。

但现在这样的代码不起作用:

cellMap[x, y].Population++;

我可以想到很多选择,但我并不真正喜欢其中任何一个。

  1. 将数组公开, 和写cellMap.Data[x, y].Population = 5;
  2. 停止使用 CellMap 类,直接使用二维数组即可。但是 CellMap 非常方便,因为它实现了自己优化的序列化,并且公开了Width and Height比书写更方便的属性cellMap.GetLength(0)
  3. 使 Cell 不可变。但是代码会是什么样子呢?cellMap[x, y] = IncrementCellPopulation(cellMap[x, y])?非常详细。
  4. 几个效用函数 like cellMap.SetPopulationAt(x, y, 5)
  5. 在每个拥有 CellMap 的类中,添加公用事业财产 like private Cell[,] CellData { get { return this.CellMap.GetInternalArray(); } },那么我的代码可以是这样的CellData[x, y].Population++

传统上如何解决这个问题?


所以这里实际上有两个问题。您实际上问了一个问题:有哪些技术可以处理结构应该是不可变的这一事实,因为它们是按值复制的,但您想要改变一个。然后还有一个问题激发了这个问题,即“我怎样才能使我的程序的性能可以接受?”

我的另一个答案解决了第一个问题,但第二个问题也很有趣。

首先,如果探查器实际上已识别出性能问题是由于单元的垃圾收集造成的,那么它是possible将 cell 变成结构会有所帮助。也有可能它根本没有帮助,而且有可能这样做会让情况变得更糟。

您的单元格不包含任何引用类型;我们知道这一点是因为你说过它们只有三个字节。如果阅读本文的其他人认为他们可以通过将类转换为结构来进行性能优化,那么它可能根本没有帮助,因为该类可能包含引用类型的字段,在这种情况下,垃圾收集器仍然必须收集每个实例,即使它被转换为值类型。里面的参考类型也需要收集!如果 Cell 仅包含值类型(显然确实如此),出于性能原因,我建议尝试此操作。

这可能会让情况变得更糟,因为值类型并不是万能的。他们也有成本。复制值类型通常比引用类型更昂贵(引用类型几乎总是寄存器的大小,几乎总是在适当的内存边界上对齐,因此芯片针对复制它们进行了高度优化)。并且值类型始终被复制。

现在,在你的情况下,你有一个结构,它是smaller比参考;引用通常是四个或八个字节。并且您将它们放入一个数组中,这意味着您正在将数组打包;如果有一千个,则需要三千字节。这意味着每四个结构中有三个未对准的,意味着有更多时间(在许多芯片架构上)从阵列中获取值。您可以考虑衡量以下因素的影响padding你的结构体变成了四个字节,看看这是否有什么不同,前提是你仍然将它们保存在一个数组中,这让我想到了下一点......

Cell 抽象可能只是一个糟糕的抽象用于存储大量细胞的数据。如果问题是 Cell 是类,您要保留数千个 Cell 的数组,并且收集它们的成本很高,那么除了将 Cell 制作为结构体之外,还有其他解决方案。例如,假设一个 Cell 包含两个字节的 Population 和一个字节的 Color。那就是机制Cell,但肯定不是界面你想暴露给用户。您的机制没有理由必须使用与接口相同的类型。因此你可以按需制造 Cell 类的实例:

interface ICell
{
   public int Population { get; set; }
   public Color Color { get; set; }
}
private class CellMap
{
    private ushort[,] populationData; // Profile the memory burden vs speed cost of ushort vs int
    private byte[,] colorData; // Same here. 
    public ICell this[int x, int y] 
    {
        get { return new Cell(this, x, y); }
    }

    private sealed class Cell : ICell
    {
        private CellMap map;
        private int x;
        private int y;
        public Cell(CellMap map, int x, int y)
        {
            this.map = map; // etc
        }
        public int Population  
        {
            get { return this.map.populationData[this.x, this.y]; } 
            set { this.map.populationData[this.x, this.y] = (ushort) value; } 
        }

等等。按需制造电池。如果它们的寿命很短,它们几乎会立即被收集。CellMap 是一个抽象, so 使用抽象来隐藏杂乱的实现细节。

使用这种架构,您不会遇到任何垃圾收集问题,因为您几乎没有活动的 Cell 实例,但您仍然可以说

map[x,y].Population++;

没问题,因为第一个索引器创建了一个不可变的对象知道如何更新地图的状态. The Cell不需要是可变的;请注意,Cell 类是完全不可变的。 (哎呀,这里的 Cell 可能是一个结构体,当然,将其转换为 ICell 无论如何都会将其装箱。)map这是可变的,并且单元格会为用户改变地图。

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

如何处理返回结构的不可变性? 的相关文章

  • 静态类变量与外部变量相同,只是具有类作用域吗?

    在我看来 静态类变量与外部变量相同 因为你只需要declare它在static int x extern int x语句 并在其他地方实际定义它 通常在 cpp 文件中 静态类变量 h file class Foo static int x
  • Qt 计算和比较密码哈希

    目前正在 Qt 中为测验程序构建面向 Web 的身份验证服务 据我了解 在数据库中存储用户密码时 必须对其进行隐藏 以防落入坏人之手 流行的方法似乎是添加的过程Salt https en wikipedia org wiki Salt cr
  • Paradox 表 - Oledb 异常:外部表不是预期的格式

    我正在使用 Oledb 从 Paradox 表中读取一些数据 我遇到的问题是 当我将代码复制到控制台应用程序时 代码可以工作 但在 WinForms 中却不行 两者都以 x86 进行调试 我实际上只是复制代码 在 WinForms 应用程序
  • 有没有办法使用 i387 fsqrt 指令获得正确的舍入?

    有没有办法使用 i387 fsqrt 指令获得正确的舍入 除了改变精确模式在 x87 控制字中 我知道这是可能的 但这不是一个合理的解决方案 因为它存在令人讨厌的重入型问题 如果 sqrt 操作中断 精度模式将出错 我正在处理的问题如下 x
  • 在 MATLAB 中创建共享库

    一位研究人员在 MATLAB 中创建了一个小型仿真 我们希望其他人也能使用它 我的计划是进行模拟 清理一些东西并将其变成一组函数 然后我打算将其编译成C库并使用SWIG https en wikipedia org wiki SWIG创建一
  • 默认值 C# 类 [重复]

    这个问题在这里已经有答案了 我在控制器中有一个函数 并且我收到表单的信息 我有这个代码 public Actionresult functionOne string a string b string c foo 我尝试将其转换为类似的类
  • 防止GDB中的PLT(过程链接表)断点

    在最新版本的 GDB 中 在库函数调用上设置断点会导致多个实际断点 调用过程链接表 PLT 实际的函数调用 这意味着当调用库函数时 我们每次都会经历两次中断 在以前的 GDB 版本中 只会创建 2 因此您只能得到一次中断 那么问题来了 是否
  • 更改 IdentityServer4 实体框架表名称

    我正在尝试更改由 IdentityServer4 的 PersistedGrantDb 和 ConfigurationDb 创建的默认表名称 并让实体框架生成正确的 SQL 例如 而不是使用实体IdentityServer4 EntityF
  • 在VisualStudio DTE中,如何获取ActiveDocument的内容?

    我正在 VisualStudio 中编写脚本 并尝试获取当前 ActiveDocument 的内容 这是我当前的解决方案 var visualStudio new API VisualStudio 2010 var vsDTE visual
  • 系统错误 124 - SHFileOperation 的 ERROR_INVALID_LEVEL

    我在使用时遇到问题SHFileOperation SHFileOperation SHFILEOPSTRUCT https stackoverflow com questions 9191415 shfileoperation shfile
  • 我们可以使用 EWS 托管 API 连接到 Exchange 2016 吗?

    我使用的是 EWS 托管 API 2 2 它的枚举中没有显示 Exchange 版本 2016 我可以看到 Exchange 2013 之前的版本 那么如何连接到 Exchange Server 2016 该 API 是否尚未从 Micro
  • 将非算术类型作为参数传递给 cmath 函数是否有效?

    给定以下用户定义类型S具有转换功能double struct S operator double return 1 0 以及以下调用cmath http en cppreference com w cpp header cmath使用类型的
  • 设计 Javascript 前端 <-> C++ 后端通信

    在我最近的将来 我将不得不制作一个具有 C 后端和 Web 前端的系统 要求 目前 我对此了解不多 我认为前端将触发数据传输 而不是后端 所以不需要类似 Comet 的东西 由于在该领域的经验可能很少 我非常感谢您对我所做的设计决策的评论
  • 不兼容的类型 - 是因为数组已经是指针吗?

    在下面的代码中 我创建一个基于书籍结构的对象 并让它保存多个 书籍 我设置的是一个数组 即定义 启动的对象 然而 每当我去测试我对指针的了解 实践有帮助 并尝试创建一个指向创建的对象的指针时 它都会给我错误 C Users Justin D
  • 相当于 C# 中 Java 的“ByteBuffer.putType()”

    我正在尝试通过从 Java 移植代码来格式化 C 中的字节数组 在 Java 中 使用方法 buf putInt value buf putShort buf putDouble 等等 但我不知道如何将其移植到 C 我尝试过 MemoryS
  • 如何访问窗口?

    我正在尝试使用其句柄访问特定窗口 即System IntPtr value Getting the process of Visual Studio program var process Process GetProcessesByNam
  • 在 Java 服务器中验证 Windows 用户

    我正在开发一个用 Java 编写的服务器和一个在同一网络上的 Windows 计算机上运行的客户端 用 Net 编写的桌面应用程序 我希望进行一些基本身份验证 以便服务器可以确定运行客户端的用户的用户名 而不需要用户在客户端中重新输入其 W
  • ASP.NET Core Razor Page 多路径路由

    我正在使用 ASP NET Core 2 0 Razor Pages 不是 MVC 构建系统 但在为页面添加多个路由时遇到问题 例如 所有页面都应该能够通过 abc com language 访问segment shop mypage 或
  • 为什么使用 HTTP 动词?

    因为动词的目标是像 server domain getallrecords 或 server domain delete1record 或类似的 URL 而getallrecords delete1record都是专门为特定目的而设计的 为
  • 如何获取 QIcon 的文件/资源​​路径

    假设我做了这样的事情 QIcon myIcon resources icon ico 我稍后如何确定该图标的路径 例如 QString path myIcon getPath 问题是 没有getPath 会员 我找不到类似的东西 但肯定有办

随机推荐

  • 如何检测 2 的补码乘法溢出?

    在我正在阅读的一本书中 以下函数用于确定 2 的补码整数乘法溢出 int tmult ok int x int y int p x y return x p x y 虽然这有效 但我如何证明它在所有情况下的正确性 当发生溢出时如何确保 p
  • 在 Windows Server 上运行的 Java 应用程序可以通过 Windows 身份验证连接到 SQL Server

    在提出问题之前 让我先介绍一些背景知识 我在一家主要运行 Windows 的商店 我们有几个批处理应用程序在 Windows 服务器上运行 主要是 2003 年 大多数批处理应用程序都是用 C 和 C 编写的 然而 我们有一些用 Java
  • 如何通过命令行检查 Visual Studio 更新?

    为了简化我的虚拟环境设置 我正在使用巧克力味 http chocolatey org自动化我的虚拟机 因为我可以运行cinst安装 Visual Studio 的命令 c gt cinst VisualStudio2012Professio
  • tangelgram 的彩色线 - 包 ape 函数 cophyloplot

    我正在尝试对包含相同分类单元的两棵树进行系统发育比较 我想根据隔离站点为连接着色 我原以为我已经成功执行了此操作 但我的工作流程中存在错误 即彩色线与隔离站点不准确对应 我想知道您是否有任何见解 请在下面找到我的可复制示例 site lt
  • firebase导入服务抛出错误

    我正在使用 firebase 函数 我想使用服务帐户密钥 json 来初始化App 并将其放入凭证中 但出现错误 类型参数 type string project id 字符串 private key id 字符串 私钥 字符串 clien
  • 从字节数组中读取 C# 中的 C/C++ 数据结构

    从数据来自 C C 结构的 byte 数组填充 C 结构的最佳方法是什么 C 结构看起来像这样 我的 C 很生锈 typedef OldStuff CHAR Name 8 UInt32 User CHAR Location 8 UInt32
  • 随着利润增加,如何在止损和当前价格之间保持 10 点的利润差距

    我试图在解决方案中添加另一个条件 当交易盈利 10 点时 我希望止损移动 10 点 更具体地说 假设我设置了一个挂单买单 止损是低于开盘价 10 点 止盈是 50 点 如果交易盈利 10 点 则止损将向上移动 10 点 如果交易盈利 20
  • 有没有更好的写法 ||在单个 if 语句中[重复]

    这个问题在这里已经有答案了 想知道是否有更好的方法来编写下面的代码 我希望我的 if 语句忽略所有键码 但这样写似乎很混乱 如下所示 thanks if event keyCode 9 event keyCode 91 event keyC
  • 为什么 ((object)(int)1).Equals(((object)(ushort)1)) 产生 false?

    我有这样的情况object我想检查与另一个是否相等object public static bool Equals object a object b return a Equals b 当出现问题时a 1 integer and b 1
  • 为什么 Java 中的每个对象都隐式扩展 java.lang.Object 类?

    我已经用 Java 编程有一段时间了 但是当我试图解释什么是 Java 时 java lang Object是给朋友上课的 我除了简单的一句台词之外想不出更多的话 Java中的所有对象都扩展java lang Object隐含地 我不太确定
  • 以编程方式创建具有特定样式的 ImageView

    我想以编程方式执行此操作
  • IOS 5:UIScrollView 不将触摸传递给 nextResponder

    我有一个子类UIScrollView 并覆盖其所有 4 Touches 功能 在那些 Touches 函数中 self nextResponder Touches and super Touches 用于传递触摸事件 我还有一个视图控制器
  • Apache POI 设置 Excel 图表标题

    我正在从头开始创建 Excel 工作簿 其中一张工作表包含一个图表 我想设置图表标题 Apache POI 在 HSSFChart 上有一个 setChartTitle 方法 但 XSSFChart 和与格式无关的 Chart 都没有设置图
  • 如何验证 WebClient 请求?

    我正在使用网络客户端调用我网站上的页面 我正在尝试将网页的结果放入 pdf 中 因此我正在尝试获取呈现页面的字符串表示形式 问题是请求没有经过身份验证 所以我得到的只是一个登录屏幕 我已将 UseDefaultCredentials 属性设
  • 如何向 django 项目添加环境变量

    我正在尝试设置我的项目 以便它可以在本地使用环境变量 我尝试将其添加到我的激活文件和其他内容列表的末尾 我正在尝试使用这个 from base import if os environ DJANGO SERVER TYPE local tr
  • 在事务内部或外部打开和关闭游标以及事务失败时如何关闭游标

    我正在 SQL Server 2012 中编写一个存储过程 它使用游标进行读取 并在TRY CATCH堵塞 基本上 我的问题如下 我应该在里面声明我的光标吗TRY CATCH堵塞 如果是 我应该在之前还是之后声明光标BEGIN TRANSA
  • 将 Font Awesome 图标做成一个圆圈?

    我在某些项目中使用 font Awesome 但我想用 font Awesome 图标做一些事情 我可以轻松地调用这样的图标 i class fa fa lock i 是否有可能所有图标始终处于带边框的圆圈中 类似这样的 我有一张图片 Us
  • 使用一组迭代步骤迭代基于列表的列表

    我想根据存储在另一个列表中的可变数量的迭代和存储为整数的恒定数量的跳过来迭代给定的列表 假设我有三件事 l 我需要迭代 或过滤 的列表 w 一个列表 告诉我在休息之前要迭代多少项 k 一个整数 告诉我在每组迭代之间要跳过多少个元素 换句话说
  • Android 上原生的自修改代码

    我正在尝试在 Android 上制作一些自修改本机代码并在模拟器中运行它 我的示例基于 android ndk 中的 Hello JNI 示例 它看起来像这样 define NOPE LENGTH 4 typedef void FUNC v
  • 如何处理返回结构的不可变性?

    我正在编写一个具有巨大的二维 单元 数组的游戏 一个单元仅占用 3 个字节 我还有一个名为 CellMap 的类 其中包含作为私有字段的 2D 数组 并提供通过公共索引器对其进行访问 分析表明 性能问题是由过多 Cell 对象的垃圾回收引起