阅读更多

0顶
0踩

编程语言

翻译新闻 如何减少Ruby中的内存使用

2018-02-14 10:20 by 副主编 jihong10102006 评论(0) 有8384人浏览
引用
原文:Reducing Memory Usage in Ruby
作者:tenderlove
翻译:无阻我飞扬

摘要:Ruby的GC用于垃圾回收,释放内存给回系统。本文作者以代码示例介绍了Ruby中的指令序列、指令序列格式以及指令序列的引用和压缩,重点引出了如何在Ruby中节省内存。以下是译文。

本人一直在努力用Ruby构建一个压缩垃圾收集器,而实现压缩GC(垃圾收集器)的最大障碍之一就是更新引用。例如,如果对象A指向对象B,但是压缩GC移动对象B,那么如何确保对象A指向对象B的新位置呢?

对大多数对象来说,解决这个问题是相当直接的。Ruby的垃圾收集器熟知大多数Ruby对象的内部结构,所以在压缩器运行以后,它会遍历所有的对象,并更新其内部指向任何移动对象的新位置。如果GC不知道某个对象的内部结构(例如在C扩展中实现的对象),则不允许该对象引用的内容移动。例如,对象A指向对象B。如果GC不知道如何更新对象A的内部结构,它将不允许对象B移动(称之为“钉住”一个对象)。

当然,允许移动的对象越多越好。

前面提到过,更新大多数对象的引用是相当直接的。不幸的是,有一根刺在我身边有一段时间了,那就是指令序列。

指令序列

当Ruby代码被编译的时候,它被转换为指令序列对象,而这些对象是Ruby对象。通常不会与这些Ruby对象交互,但它们就在那里。这些对象为Ruby应用程序存储字节码,代码中的任何文字,以及关于被编译的代码(源位置,覆盖信息等等)的一些其它杂项信息。

这些指令序列对象在内部被称为“IMEMO”对象。IMEMO对象有多个子类型,指令序列子类型为“iseq”。如果使用的是Ruby2.5,并且使用ObjectSpace堆转储,则会看到转储现在包含这些IMEMO子类型。接下来看一个例子。

在Rails应用程序中,我一直在使用下面的代码来堆转储:
require 'objspace'
require 'config/environment'

File.open('output.txt', 'w') do |f|
  ObjectSpace.dump_all(output: f)
end

上面的代码将内存中的所有对象以JSON行格式输出到名为“output.txt”的文件中。下面是来自Rails堆转储的两个IMEMO记录:
{
  "address": "0x7fc89d00c400",
  "type": "IMEMO",
  "class": "0x7fc89e95c130",
  "imemo_type": "ment",
  "memsize": 40,
  "flags": {
    "wb_protected": true,
    "old": true,
    "uncollectible": true,
    "marked": true
  }
}

{
  "address": "0x7fc89d00c2e8",
  "type": "IMEMO",
  "imemo_type": "iseq",
  "references": [
    "0x7fc89d00c270",
    "0x7fc89e989a68",
    "0x7fc89e989a68",
    "0x7fc89d00ef48"
  ],
  "memsize": 40,
  "flags": {
    "wb_protected": true,
    "old": true,
    "uncollectible": true,
    "marked": true
  }
}

这个例子来自Ruby2.5,所以两个记录都包含一个imemo_type字段。第一个例子是“ment”或“method entry”,第二个例子是“iseq”或“指令序列”。接下来看看指令序列。

指令序列的格式

指令序列是编译Ruby代码的结果。指令序列是Ruby代码的二进制表示形式。这些指令存储在指令序列对象中,具体是这个iseq_encoded字段(iseq_size是iseq_encoded字段的长度)。

如果检查iseq_encoded,会发现这只是一个数字列表。数字列表是虚拟机指令以及指令的参数(操作数)。

如果检查这个iseq_encoded列表,它可能看起来像这样:
地址 描 述                           
0 0x00000001001cddad 指令(0个操作数)
1 0x00000001001cdeee 指令(2个操作数)
2 0x00000001001cdf1e 操作数
3 0x000000010184c400 操作数
4 0x00000001001cdeee 指令(2个操作数)
5 0x00000001001c8040 操作数
6 0x0000000100609e40 操作数
7 0x0000000100743d10 指令(1个操作数)
8 0x00000001001c8040 操作数
9 0x0000000100609e50 指令(1个操作数)
10 0x0000000100743d38 操作数

列表中的每个元素对应于指令或指令的操作数。指令的所有操作数都遵循列表中的指令。操作数是执行相应指令所需的一切,包括Ruby对象。换句话说,其中一些地址可能是Ruby对象的地址。

由于其中一些地址可能是Ruby对象的地址,这意味着指令序列引用了Ruby对象。但是,如果指令序列引用Ruby对象,那么指令序列如何防止这些Ruby对象被垃圾收集呢?

活跃度和代码编译

正如之前所说,指令序列是编译Ruby代码的结果。在编译过程中,代码的某些部分被转换为Ruby对象,然后这些对象的地址被嵌入到字节代码中。来看一个Ruby对象何时嵌入到指令序列中的例子,然后看看这些对象是如何保持活力的。

示例代码就是puts "hello world"。可以使用RubyVM::InstructionSequence编译代码,然后反汇编它。反汇编解码iseq_encoded并打印出更可读的东西。
>> insns = RubyVM::InstructionSequence.compile 'puts "hello world"'
=> <RubyVM::InstructionSequence:<compiled>@<compiled>>
>> puts insns.disasm
== disasm: #<ISeq:<compiled>@<compiled>>================================
0000 trace            1                                               (   1)
0002 putself          
0003 putstring        "hello world"
0005 opt_send_without_block <callinfo!mid:puts, argc:1, FCALL|ARGS_SIMPLE>, <callcache>
0008 leave            
=> nil
>>

指令003是putstring指令。看看可以在insns.def找到的 putstring指令的定义:
/* put string val. string will be copied. */
DEFINE_INSN
putstring
(VALUE str)
()
(VALUE val)
{
    val = rb_str_resurrect(str);
}

当虚拟机执行时,它将跳转到putstring指令的位置 ,解码操作数,并将这些操作数提供给指令。在这种情况下,putstring指令有一个所谓的操作数str,它是VALUE类型,和一个称为val的返回值,它也是VALUE类型。指令体本身只是调用rb_str_resurrect,传递str和分配返回值给val。 rb_str_resurrect只是复制一个Ruby字符串。所以这个指令需要一个Ruby对象(一个已经存储在指令序列中的字符串),复制该字符串,然后虚拟机将该复制的字符串推入堆栈。为了一个有趣的练习,试着用puts "hello world".freeze来完成这个过程,并看看差异。

现在,字符串“hello world”如何在这条指令执行之前保持活跃?一些东西必须标记字符串对象,以便垃圾收集器知道引用正在被持有。

指令序列保持这些对象活跃的方式是通过使用它所谓的“标记数组”来实现的。当编译器将代码转换为指令序列时,它将为“hello world”分配一个字符串,然后将该字符串推送到一个数组上。下面是compile.c的摘录,它是这样做的:
case TS_VALUE:    /* VALUE */
{
    VALUE v = operands[j];
    generated_iseq[code_index + 1 + j] = v;
    /* to mark ruby object */
    iseq_add_mark_object(iseq, v);
    break;
}

所有的iseq_add_mark_object操作都是将VALUE推入到数组,这些数组存储着指令序列对象。 iseq是指令序列对象,并且v是想要保持活跃的VALUE(在这个案例中就是字符串“hello world”)。如果查看vm_core.h文件,可以找到该标记数组的位置以及注释:
VALUE mark_ary;     / *数组:包含应该用GC标记的操作数* /

指令序列引用和压缩

所以,指令序列包含两个字符串文字的引用:一个在iseq_encoded指令中,一个凭借标记数组。如果字符串文字移动,那么这两个位置都需要更新。更新数组内部结构是相当简单的:它只是一个列表,然而更新指令序列并不容易。

要在指令序列中更新引用,必须反汇编指令,定位每一个VALUE操作数,并更新这些位置。没有任何代码来执行这些指令,所以在这里介绍一个函数,它可以反汇编指令并调用这些对象的函数指针。这样就能够找到Ruby对象的新位置并更新指令。但是是不是可以用这个函数来做更多的事情呢?下面重点来了。

减少内存

现在终于到了关于节省内存的部分了。存储在指令序列对象中的标记数组的要点是保持指令序列引用的任何对象都是活跃的:

可以重新使用“update reference”函数来标记直接包含在指令序列中的引用。这意味着可以减少标记数组的大小:

完全消除标记数组是另外一码事,因为存储在标记数组中的东西不仅仅是文字。然而,如果直接从指令序列中标记对象,那么很少需要增加数组。节省的内存总量是数组的大小加上数组中所有未使用的额外容量

做了一个补丁来实现这个策略,可以在Ruby的GitHub fork上找到它 。

发现在基本Rails应用程序设置为生产模式时,可以节省大约3%的内存。当然,加载的代码越多,节省的内存就越多。预期这个补丁会影响GC的性能,因为反汇编指令并迭代它们应该比迭代一个数组更困难。然而,由于指令序列已成熟,并且有一代又一代的垃圾收集器,因此对真实应用程序的性能影响非常小。

我正致力于将这个补丁上传到Ruby官网,大家可以在这里关注并阅读更多有关于此的信息。
  • 大小: 68.5 KB
  • 大小: 57.8 KB
来自: CSDN
0
0
评论 共 0 条 请登录后发表评论

发表评论

您还没有登录,请您登录后再发表评论

相关推荐

  • DataGrid和CheckBox的混合使用

    我们知道DataGrid是非常强大的一个ASP.NET组件,我们可以用它表示非常丰富的信息.在论坛里经常可以看见一些网友问一些关于该控件的问题,我虽不是什么高手但是对DataGrid还是有一些了解,加上我比较喜欢学习所以我今天就将DataGrid和CheckBox的组合使用做一个简单的描述.我们可能在写程序的时候都遇到这种情况:需要选择一个列表的所有项或者取消所有项的选择来删除这些列以及如何给用户

  • DataGrip连接ClickHouse时区显示异常

    文章目录版本信息问题解决方法 版本信息 DataGrip 2021.2.4 ClickHouseDriver 0.3.1 问题 Linux服务器端正确设置ClickHouse时区,查询也正常 DataGrip客户端查询却显示小了8个小时,可以定位是时区问题 解决方法 修改ClickHouse连接属性 -&gt; Advanced。 use_server_time_zone 设置为 false use_time_zone 设置为 UTC,一定要设置为大写。 选择Apply,然后重新查询恢复正常。 ..

  • DataGrid中加入CheckBox,并实现单选

       WebForm1        http://schemas.microsoft.com/intellisense/ie5" name="vs_targetSchema">       function SetCheckBoxState()     {     var dom=document.all;     var el=event.srcElement;        if(el.ta

  • CheckBox在DataGrid上选择复选框

    CheckBox在DataGrid上选择教程 »在DataGrid上选择CheckBox本教程将向您介绍如何在DataGrid上放置复选框列。通过复选框,用户可以选择一次选择/取消选择网格行。观看演示要添加复选框列,我们只需添加一个带有复选框属性的列并将其设置为true。代码看起来像这样:&amp;lt;table id = “tt” title = “复选框选择” class = “easyui-dat...

  • datagrid + checkbox

                         AutoGenerateColumns="false" AllowSorting="True" AllowPaging="true"                     PageSize="20" PagerSettings-Mode="Numeric" CellPadding="0"                     OnRowDataBoun

  • easyui datagrid组件 单击行 让他不选中 只有单击checkbox的时候才选中

    第一:问题如图 如上图,当单击行的时候,datagrid会默认选中该行。 在easyui的api中,有两个参数: 1、checkOnSelect 如果为true,当用户点击行的时候该复选框就会被选中或取消选中。 如果为false,当用户仅在点击该复选框的时候才会呗选中或取消。 2、selectOnCheck 如果为true,单击复选框将永远选择行。 如果为false,选择行将不选中复选框。

  • easyui DataGrid checkbox 根据后台传递过来的数据进行勾选

    function loadMessage() { $('#pressure').datagrid({ height: 500, url: '/Home/RealtimeData', method: 'POST', //queryParams: { 'id': OBJECTID }, idField: 'ID',...

  • DataGrid中添加CheckBox

    在DataGrid中要实现确认功能最初的设想是:在每行前面加一个CheckBox,以实现当点击CheckBox时,立即触发事件(如:on click="Chk_Click()"),获取当前行的id和CheckBox的checked属性值,最后更新数据库。在解决问题之前,我搜过很多相关文档,大部分是如何实现全选,而且还是通过一个按钮来触发事件的,明显不符合自己的要求,但还是提供了一些信息。事实上遇到

  • 解决datagrid的checkbox选中事件和行选中事件,同时去除高亮

    在今天的开发中有这么一个需求。datagrid点击开启编辑状态不能影响checkbox的状态,同时为了界面美观。去除选中的高亮。去除高亮百度的解决办法如下: (1)修改easyui的css将高亮颜色跟背景颜色一样(简单,但是比较笨) (2)在onClickCell事件里clearSelections一下,相当于不允许用户选中。 (3)在onSelect事件里unselectRow一下 (4

  • WPF中DataGrid中的DataGridCheckBoxColumn用法(全选,全否,反选)

    check datagrid wpf

  • 在asp.net中实现datagrid checkbox 全选的方法

    <br /><form runat="server"> <br /><asp:DataGrid AutoGenerateColumns="false" OnItemCreated="itemcreate" DataKeyField="link_id" ID="mydg" runat="server" > <br /><columns> <br /><asp:TemplateColumn> <br /><headertemplate> <br /><asp:CheckBox ID="checkall" OnC

  • datagrid,checkbox混合使用

       我们知道DataGrid是非常强大的一个ASP.NET组件,我们可以用它表示非常丰富的信息.在论坛里经常可以看见一些网友问一些关于该控件的问题,我虽不是什么高手但是对DataGrid还是有一些了解,加上我比较喜欢学习所以我今天就将DataGrid和CheckBox的组合使用做一个简单的描述.我们可能在写程序的时候都遇到这种情况:需要选择一个列表的所有项或者取消所有项的选择来删除这些列以及如何

  • DataGrid中加入CheckBox,同时只能单选

    以下是.ASPX文件   WebForm1        http://schemas.microsoft.com/intellisense/ie5" name="vs_targetSchema">       function SetCheckBoxState()     {     var dom=document.all;     var el=event.srcElement;      

  • Dorado的复选框简单实现

    当然这个方法可能不是那么的理想 但我是一个菜鸟用它做几个比较小的选项还是比较可以的,好了不废话了 。     都应该知道Dorado里面有个checkbox这个控件吧 它就是用来实现复选框的,最开始用的时候我也很纳闷这单个控件怎么实现复选框的,查看了Dorado的jsdoc发现它可以直接获取选中的值,当时灵机一动,这个地方还是上图比较好 这时为什么要用onValue,因为这个属性是获取选中时候

  • easyui 合并单元格,合并复选框,复选框选中所有合并行

    $obj = $("#configQueryGrid"); $obj.datagrid({ loadMsg: '数据加载中请稍后……', url: '/getData', nowrap: true,//设置为tru...

  • EasyUI Datagrid 获得选中行(CheckBox)的使用和获取的数据不正常(数据缺失。。)的解决

    checkOnSelect            当为True的时候,点每一行时CheckBox就会被选中;为False时,只能手动的点击CheckBox ;默认true;      selectOnCheck            当为True的时候,点击CheckBox时行选中;反之要手动点击每一行;默认true; 获取行选中数据:取得第一个选中行数据,如果没有选中行,则返回 null,否则...

Global site tag (gtag.js) - Google Analytics