0

我知道 shift、push 和 pop 是用于添加/删除数组元素的数组方法,但我不确定内存中实际发生了什么。例如,pop 方法删除数组的最后一个元素。它让我想起了堆栈中使用的 LIFO 顺序,但我认为该元素并没有像汇编编程中那样真正“弹出”;而不是整个数组的索引被移动。我真的不知道,所以如果有人可以帮助我,我将不胜感激。

4

1 回答 1

7

Ruby 适用于因您的想法而受苦的程序员。我们相信那些在优化性能和内存管理方面做得最好的程序员。

如果你只是好奇,这里是 Rubinius 中 Array#shift 的代码:

def shift(n=undefined)
    Rubinius.check_frozen

    if n.equal? undefined
      return nil if @total == 0
      obj = @tuple.at @start
      @tuple.put @start, nil
      @start += 1
      @total -= 1

      obj
    else
      n = Rubinius::Type.coerce_to(n, Fixnum, :to_int)
      raise ArgumentError, "negative array size" if n < 0

      slice!(0, n)
    end
end

你可以看到,一个数组本身就是一个 Rubinius::Tuple,而在 Tuple 的定义中,它就是一个 Rubinius::Array。它所做的只是将开始位置放在下一个位置。我不确定他们会释放它使用的空间(我认为它会),因为你必须更深入地挖掘。

在官方 1.9.3 中,我不知道它是如何实现的,就像他们在 C 中那样,而且它们很难阅读。如果想了解更多细节,可以在 GitHub 上 fork Rubinius,或者从 ruby​​-lang.org 上 fork 官方 1.9.3,并阅读源码。您也可以了解有关 C/C++ 编程的更多信息 :)


于是我快速浏览了官方1.9.3的代码,这是array#shift函数的定义:

static VALUE
rb_ary_shift_m(int argc, VALUE *argv, VALUE ary)
{
    VALUE result;
    long n;

    if (argc == 0) {
    return rb_ary_shift(ary);
    }

    rb_ary_modify_check(ary);
    result = ary_take_first_or_last(argc, argv, ary, ARY_TAKE_FIRST);
    n = RARRAY_LEN(result);
    if (ARY_SHARED_P(ary)) {
    if (ARY_SHARED_NUM(ARY_SHARED(ary)) == 1) {
        rb_mem_clear(RARRAY_PTR(ary), n);
    }
        ARY_INCREASE_PTR(ary, n);
    }
    else {
    MEMMOVE(RARRAY_PTR(ary), RARRAY_PTR(ary)+n, VALUE, RARRAY_LEN(ary)-n);
    }
    ARY_INCREASE_LEN(ary, -n);

    return result;
}

这一行:

MEMMOVE(RARRAY_PTR(ary), RARRAY_PTR(ary)+n, VALUE, RARRAY_LEN(ary)-n);

它告诉我们它实际上将内存块偏移量移动了 n。这大概就是官方运行速度比Rubinius慢的原因吧……Rubinius在大内存上占优势,但节省时间;官方消耗更少的内存,但需要更多的时间......

于 2012-04-21T03:36:48.787 回答