【问题标题】:What is the actual overhead from wrapping objects? [closed]包装对象的实际开销是多少? [关闭]
【发布时间】:2013-10-05 17:28:54
【问题描述】:

我有一个从第三方库获取数据的时间敏感型应用程序。将它们的对象包装到更适合我的应用程序的接口中会对性能造成什么影响?

注意:我在这里发布答案(问答风格),但如果有错误请纠正我!

【问题讨论】:

  • 这取决于包装的类型。

标签: java performance creation overhead


【解决方案1】:

间接有一些开销,但很难衡量。 OP 的基准测试每次迭代大约需要 4 ns,而我的需要大约 1 ns(对于最快的实验)。这意味着他们测量的主要是ArrayListIteratorcycle 的开销,可能还包括虚拟调用开销。

要测量的开销是如此之小,以至于您需要使用数组并添加内部循环或使用掩码来访问它们。

我的benchmark 中的results 表明,使用接口和间接都存在可测量的开销。这个开销可能在 20% 到 50% 之间,看起来很多。然而,重要的部分是 20-50%。它只是特制基准测试的一小部分,除了执行代码之外什么都不做。在任何现实的片段代码中,相对开销都会低十倍、一百倍或千倍。

因此,除非您正在设计一个高性能库来执行一些非常基本且快速的操作,否则请忘记它。随意使用间接和接口,专注于好的设计。即使性能很重要,您可能还有其他地方可以获得更多。

【讨论】:

  • 我认为在这种情况下,开销作为一个百分比而不是作为一个绝对值更重要。你的最后一段解释了原因。感谢您为同意我的回答所做的工作。虽然性能在我的应用程序中非常重要,但我很高兴看到我可以通过不包装我的数据提要来停止我的头撞墙,而不必担心显着的性能损失。
【解决方案2】:

在尝试了几个月的课程后,今天我决定进行测试。似乎它不会增加太多开销。以下是结果 - 实际上并不是那么一致,这是一个实际上已经展开的结果更慢:

 0% Scenario{vm=java, trial=0, benchmark=Unwrapped} 3.96 ns; ?=0.02 ns @ 3 trials
33% Scenario{vm=java, trial=0, benchmark=Copy} 3.93 ns; ?=0.01 ns @ 3 trials
67% Scenario{vm=java, trial=0, benchmark=Backing} 3.94 ns; ?=0.01 ns @ 3 trials

benchmark   ns linear runtime
Unwrapped 3.96 ==============================
     Copy 3.93 =============================
  Backing 3.94 =============================

vm: java
trial: 0

源代码(Caliper 0.5-rc1,Guava 2.0+):

import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.Random;

import com.google.caliper.Runner;
import com.google.caliper.SimpleBenchmark;
import com.google.common.collect.Iterables;

public class WrapperTest {
    public static class Unwrapped {
        private int inner;

        public Unwrapped(int inner) {
            this.inner = inner;
        }

        public int getInner() {
            return inner;
        }
    }

    public static interface Outer {
        public int getOuter();
    }

    public static class CopyOuter implements Outer {
        private int outer;

        public CopyOuter(int outer) {
            this.outer = outer;
        }

        public int getOuter() {
            return outer;
        }
    }

    public static class BackingOuter implements Outer {
        private Unwrapped inner;

        public BackingOuter(Unwrapped inner) {
            this.inner = inner;
        }

        public int getOuter() {
            return inner.getInner();
        }
    }

    public static class TestBenchmark extends SimpleBenchmark {
        private Iterable<Unwrapped> cycle;

        @Override
        protected void setUp() {
            List<Unwrapped> backing = new ArrayList<Unwrapped>(16384);
            Random r = new Random();
            for(int i = 0; i < 16384; i++) {
                backing.add(new Unwrapped(Math.abs(r.nextInt())));
            }
            cycle = Iterables.cycle(backing);
        }

        public long timeUnwrapped(int reps) {
            long total = 0;
            Iterator<Unwrapped> iter = cycle.iterator();
            for(int i = 0; i < reps; i++) {
                total += iter.next().getInner();
            }
            return total;
        }

        public long timeCopy(int reps) {
            long total = 0;
            Iterator<Unwrapped> iter = cycle.iterator();
            for(int i = 0; i < reps; i++) {
                total += new CopyOuter(iter.next().getInner()).getOuter();
            }
            return total;
        }

        public long timeBacking(int reps) {
            long total = 0;
            Iterator<Unwrapped> iter = cycle.iterator();
            for(int i = 0; i < reps; i++) {
                total += new BackingOuter(iter.next()).getOuter();
            }
            return total;
        }
    }

    public static void main(String[] args) {
        Runner.main(TestBenchmark.class, new String[0]);
    }
}

【讨论】:

  • 我真的不认为您的基准测试如此精确,因此您可以说 3.93 确实比 3.96 快。我猜测量误差一般约为 5%,在你的情况下可能更多,因为像 cycle 这样的东西可能会主导运行时间。此外,微基准测试和实际应用程序中的性能差异可能更大。
  • 我不会关心包装的成本,除非你包装了多次执行的琐碎方法。我猜 JIT 可以优化相当多的成本。
  • @maaartinus 感谢您的回复。是的,我认为 3.93 大约等于 3.96。创建循环迭代发生在setUp,所以它不会影响运行时。我这样做只是为了确保不会出现IndexOutOfBounds 异常。
  • 我的问题是它的开销可能比您要测量的要大。我会改为在数组上进行内部循环。当您使用一千个元素时,必须显示 L1 缓存未命中。
  • @maaartinus 如果你能给我一些代码作为答案,我很乐意投票
猜你喜欢
  • 2017-02-20
  • 1970-01-01
  • 2017-02-08
  • 2019-09-09
  • 1970-01-01
  • 2010-10-18
  • 2016-10-08
  • 2021-07-26
  • 1970-01-01
相关资源
最近更新 更多