迭代对象数组时删除对象的引用

时间:2014-09-15 10:18:13

标签: ruby

我有以下示例代码。我有一个Test对象,数量为10,然后准备被销毁。我有一个类,它是这些对象数组的包装器。此类每帧使用update方法,并遍历Test个对象的数组并更新每个对象。如果当前对象已达到最大计数,则删除对该对象的引用,并且当迭代完成时,该数组将变为紧凑。

require 'benchmark'
class Test
  def update
    @counter ||= 0
    @counter += 1
  end
  def destroy?
    @counter > 10
  end
  def dispose
    # dispose this object
  end
end

class Tests
  def initialize(number)
    @data = []
    number.times do 
      @data.push(Test.new)
    end
  end
  def add_data(data)
    @data.push(data)
  end
  def update
    @data.each_with_index do |t, index|
      t.update
      if t.destroy?
        t.dispose # need to dispose the sprite object in my realcode before delete ref.
        @data[index] = nil
      end
    end.compact!
  end
  # this would be another attempt, which is a little bit faster
  # but this wont work because in my case the objects I use are
  # sprite objects which needs to be disposed like shown above
  # before the reference is removed
  def update_2
    @data.each(&:update)
    @data.delete_if{|obj| obj.destroy?}
  end
end

iterations = 2000000

Benchmark.bm do |x|
  x.report {Tests.new(iterations).update}
  x.report {Tests.new(iterations).update_2}
end

一个问题是,如果添加了许多对象,这会导致性能变差。另一个问题是,每个帧,另一个对象可以通过add_data方法添加。我想知道是否可以采用另一种方法来实现这一目标,以使其不会对性能造成太大影响。

修改的 我使用自定义库(http://rmvxace.wikia.com/wiki/RGSS),因此我编辑了上面的代码,以便更清楚地了解更多内容。但主要问题仍然是更新方法。

1 个答案:

答案 0 :(得分:1)

我可以看到代码的一些性能问题,尽管其他人可能做得更好。

这就是我的想法:

  1. 你在数组中存储nil对象而不是删除引用然后你正在调用压缩...这会在数组上迭代两次而不是一次。
  2. 您的更新和删除方法可能适合一个keep_if方法调用
  3. 您可能会更好地使用initialize方法初始化对象,而不是使用延迟初始化(尽管可能会或可能不会在您创建的基准测试中显示) - 您对每次更新调用都有额外的计算。
  4. 此外,您的基准测试始终会创建一个对象并对其进行一次更新 - 这不是比较的最佳基础。

    例如,您的update_2实际上会慢得多......您根本没有注意到基准测试。

    我更新了你的update_2方法并添加了一个update_3方法,这个方法在新的基准测试中更快一些。

    update_3方法需要对Test类进行调整,该类现在有一个名为update_or_destroy的新方法,对于update,返回true,对于destroy,返回false。

    我还添加了一个初始化方法来减少延迟初始化效果(在我的测试过程中,它表明它是减少工作负载的有效方法)。

    试试这个:

    require 'benchmark'
    class Test
      def initialize
        @counter ||= 0
      end
      def update
        @counter += 1
      end
      def destroy?
        @counter > 10
      end
      def dispose
        # dispose this object
      end
      def update_or_destroy
        update
        if destroy?
          dispose
          false
        else
          true
        end
      end
    end
    
    class Tests
      def initialize(number)
        @data = []
        number.times do 
          @data.push(Test.new)
        end
      end
      def add_data(data)
        @data.push(data)
      end
      def update
        @data.each_with_index do |t, index|
          t.update
          if t.destroy?
            t.dispose # need to dispose the sprite object in my realcode before delete ref.
            @data[index] = nil
          end
        end.compact!
      end
      # this would be another attempt, which is a little bit faster
      # but this wont work because in my case the objects I use are
      # sprite objects which needs to be disposed like shown above
      # before the reference is removed
      def update_2_updated
        # @data.each(&:update)
        @data.delete_if{|obj| obj.update; obj.destroy?}
      end
    
      def update_3
        @data.keep_if {|obj| obj.update_or_destroy}
      end
    end
    
    iterations = 2000000
    
    Benchmark.bm do |x|
      x.report {Tests.new(iterations).update}
      x.report {Tests.new(iterations).update_2_updated}
      x.report {Tests.new(iterations).update_3}
    end
    
    #new benchmarks
    iterations = 100000
    test_object = Tests.new(iterations)
    puts Benchmark.measure { iterations.times { test_object.update  } }
    
    test_object = Tests.new(iterations)
    puts Benchmark.measure { iterations.times { test_object.update_2_updated  } }
    
    test_object = Tests.new(iterations)
    puts Benchmark.measure { iterations.times { test_object.update_3  } }