代码设计:性能与可维护性

Code design: performance vs maintainability

情境化

我正在测试上下文中使用 soot 框架实现字节码检测器,我想知道哪种设计更好。

我正在为我正在检测的 Class 中的每个方法构建 TraceMethod 对象,我想 运行 在多个 Class 上使用此检测器。

哪个选项提供更多性能(Space–时间)?

选项 1:(地图)

public class TraceMethod {
    boolean[] decisionNodeList;
    boolean[] targetList;
    Map<Integer,List<Integer>> dependenciesMap;
    Map<Integer,List<Double>> decisionNodeBranchDistance;
}

选项 2:(对象)

public class TraceMethod {
    ArrayList<Target> targets = new ArrayList<Target>(); 
    ArrayList<DecisionNode> decisionNodes = new ArrayList<DecisionNode>();
}

public class DecisionNode {
    int id;
    Double branchDistance;
    boolean reached;
}

public class Target {
    int id;
    boolean reached;
    List<DecisionNode> dependencies;
}

我自己实施了 选项 2,但我的老板建议我使用 选项 1,他认为那是 "lighter".我在这篇文章“Class Object vs Hashmap”中看到 HashMap 使用的内存比对象多,但我仍然不相信我的解决方案(选项 2)更好。

这是一个简单的细节,但我想确保我使用的是最佳解决方案,我关心的是性能(Space–时间)。我知道第二种选择在可维护性方面更好,但如果它不是最优的,我可以牺牲它。

方法 1 有可能更快并且使用更少 space。

特别是对于字节码工具,我会首先实现方法 1。
然后当它工作时,将两个列表替换为使用原始类型而不是 Integer 和 Double object 的非泛型列表。

请注意,int 需要 4 个字节,而 Integer (Object) 需要 16 - 20 个字节,具体取决于机器(PC 为 16,android 为 20)。

列表可以替换为使用原始整数的GrowingIntArray(如果我没记错的话,我在Apache的统计包中找到了)。 (或者一旦你知道内容不能再改变,可能只是被一个 int[] 取代) 然后你只需编写自己的 GrowingDoubleArray(或使用 double[])

记住 Collections 方便但速度较慢。
Objects 使用的 space 是原语的 4 倍。

字节码检测器需要性能,它不是每周 运行 一次的软件。

最后我不会用非通用的地图替换那个地图,这似乎 对我来说很多工作。但您可以在最后一步尝试。

作为最后的优化步骤:查看您的列表或地图中有多少元素。如果通常小于 16(你必须尝试一下),你可以切换到线性搜索, 对于非常少的元素,这是最快的。 一旦元素数量超过特定数量,您甚至可以让您的代码智能地切换搜索算法。 (Sun/Oracle java 在他们的一些 Collections 中这样做,并且 Apple/ios,到)。 然而,这最后一步会使您的代码复杂得多。

Space 例如:
DecisionNode:16 代表 class + 4 (id) + 20 (Double) +4 (boolean) = 44 + 4 padding to then next multiple of 8 = 48 bytes.

一般来说,您应该总是进行维护,而不是为了预期的性能。这有几个很好的理由:

  • 我们往往对数组与 HashMap 之间的速度差异着迷,但在实际的企业应用程序中,这些差异不足以说明应用程序速度的明显差异。
  • 应用程序中最常见的瓶颈在数据库或网络中。
  • JVM 在一定程度上优化了代码

由于可维护的代码,您的应用程序不太可能出现性能问题。更有可能的情况是,当您拥有数百万行无法维护的代码时,您的老板会 运行 没钱了。