成武城乡住房建设局网站,昆明贤邦网站建设,网站建设认准猪八戒网,营销型网站建设实训报告个人总结关于“ web scale ”这个流行词有很多炒作#xff0c;人们花了很多时间来重新组织他们的应用程序体系结构#xff0c;以使其系统“规模化”。 但是什么是扩展#xff0c;我们如何确保可以扩展#xff1f; 缩放的不同方面 上面提到的炒作主要是关于扩展负载 #xff0c;即… 关于“ web scale ”这个流行词有很多炒作人们花了很多时间来重新组织他们的应用程序体系结构以使其系统“规模化”。 但是什么是扩展我们如何确保可以扩展 缩放的不同方面 上面提到的炒作主要是关于扩展负载 即确保一个适用于1个用户的系统也适用于10个用户100个用户或数百万个用户。 理想情况下您的系统应尽可能“无状态”以便可以在网络中的任何处理单元上转移和转换真正保留的少量状态。 当负载是您的问题时延迟可能就没有了因此如果单个请求花费50-100毫秒就可以了。 这通常也称为横向扩展 扩展的一个完全不同的方面是扩展性能 即确保适用于1条信息的算法也适用于10条或100条或数百万条。 Big O Notation最好地描述了这种缩放是否可行。 延迟是扩展性能的杀手。 您想尽一切可能将所有计算保持在一台计算机上。 这通常也称为放大 如果有免费午餐之类的东西 没有 我们可以无限期地结合扩大规模和扩大规模。 无论如何今天我们将研究一些非常简单的方法来改善性能。 大O符号 Java 7的ForkJoinPool以及Java 8的并行Stream有助于并行化内容这在将Java程序部署到多核处理器计算机上时非常有用。 与在网络上的不同计算机上进行扩展相比这种并行性的优势在于您几乎可以完全消除延迟影响因为所有内核都可以访问同一内存。 但是不要被并行性的效果所迷惑 请记住以下两件事 并行主义吞噬了您的核心。 这对于批处理非常有用但是对于异步服务器例如HTTP来说却是一场噩梦。 在过去的几十年中我们使用单线程servlet模型是有充分的理由的。 因此并行性仅在扩大规模时有用。 并行性对算法的Big O表示法没有影响。 如果您的算法是O(n log n) 并且让该算法在c核上运行那么您仍然会拥有O(n log n / c)算法因为c在算法复杂度上是微不足道的常数。 您将节省挂钟时间但不会降低复杂性 当然提高性能的最好方法是降低算法复杂度。 当然杀手是实现O(1)或准O(1) 例如HashMap查找。 但这并不总是可能的更不用说轻松了。 如果您不能降低复杂性只要找到合适的位置只要对算法真正重要的地方进行调整您仍然可以获得很多性能。 假定算法的以下直观表示形式 如果我们要处理单个数量级该算法的总体复杂度为O(N 3 )或O(N x O x P) 。 但是在分析此代码时您可能会发现一个有趣的场景 在开发框中左分支 N - M - Heavy operation 是您可以在探查器中看到的唯一分支因为O和P的值在开发样本数据中很小。 但是在生产中右分支 N - O - P - Easy operation或NOPE 确实会造成麻烦。 您的运营团队可能已经使用AppDynamics或DynaTrace或某些类似的软件解决了这一问题。 没有生产数据您可能会Swift得出结论并优化“繁重的操作”。 您将其运送到生产环境并且修复无效。 除了以下事实外没有最佳的黄金法则 设计良好的应用程序更容易优化 过早的优化不会解决任何性能问题但是会使您的应用程序设计欠佳从而使优化变得更加困难 足够的理论。 假设您已找到问题所在的正确分支。 很容易在生产中吹起一个非常简单的操作因为它被称为很多次如果N O和P大。 在不可避免的O(N 3 )算法的叶子节点存在问题的情况下请阅读本文。 这些优化不会帮助您扩展规模。 他们将帮助您暂时节省客户的时间将整个算法的困难改进推迟到以后 以下是Java中最容易进行的10个性能优化 1.使用StringBuilder 这几乎是所有Java代码中的默认设置。 尽量避免使用运算符。 当然您可能会争辩说它仍然只是StringBuilder语法糖例如 String x a args.length b; …编译成 0 new java.lang.StringBuilder [16]3 dup4 ldc String a [18]6 invokespecial java.lang.StringBuilder(java.lang.String) [20]9 aload_0 [args]
10 arraylength
11 invokevirtual java.lang.StringBuilder.append(int) : java.lang.StringBuilder [23]
14 ldc String b [27]
16 invokevirtual java.lang.StringBuilder.append(java.lang.String) : java.lang.StringBuilder [29]
19 invokevirtual java.lang.StringBuilder.toString() : java.lang.String [32]
22 astore_1 [x] 但是会发生什么如果以后需要用可选部分修改String呢 String x a args.length b;if (args.length 1)x x args[0]; 现在您将拥有第二个StringBuilder 它只是不必要地消耗了堆内存从而给GC带来了压力。 改写这个 StringBuilder x new StringBuilder(a);
x.append(args.length);
x.append(b);if (args.length 1);x.append(args[0]); 带走 在上面的示例中如果您使用显式StringBuilder实例或者依赖Java编译器为您创建隐式实例则可能完全不相关。 但是请记住我们在NOPE分支中 。 每个CPU周期我们都在浪费像GC这样愚蠢的东西或分配StringBuilder的默认容量我们浪费的时间是N x O x P次。 根据经验请始终使用StringBuilder而不是运算符。 如果可以的话如果您的String构建比较复杂则可以在多个方法中保留StringBuilder引用。 这是jOOQ在生成复杂的SQL语句时所做的。 只有一个StringBuilder可以“遍历”您的整个SQL AST抽象语法树 为了大声喊叫如果仍然有StringBuffer引用请用StringBuilder替换它们。 您实际上几乎不需要同步正在创建的字符串。 2.避免使用正则表达式 正则表达式相对便宜且方便。 但是如果您位于NOPE分支中 那么它们将是您最糟糕的事情。 如果您绝对必须在计算密集型代码节中使用正则表达式则至少要缓存Pattern引用而不要一直重新编译它 static final Pattern HEAVY_REGEX Pattern.compile((((X)*Y)*Z)*); 但是如果你的正则表达式真的很傻 String[] parts ipAddress.split(\\.); ……那么您真的最好使用普通的char[]或基于索引的操作。 例如这个完全不可读的循环执行相同的操作 int length ipAddress.length();
int offset 0;
int part 0;
for (int i 0; i length; i) {if (i length - 1 || ipAddress.charAt(i 1) .) {parts[part] ipAddress.substring(offset, i 1);part;offset i 2;}
} ……这也说明了为什么您不应该进行任何过早的优化。 与split()版本相比这是无法维护的。 挑战读者中的聪明人可能会找到更快的算法。 带走 正则表达式很有用但要付出一定的代价。 如果您深入了解NOPE分支 则必须不惜一切代价避免使用正则表达式。 提防各种使用正则表达式的JDK String方法例如String.replaceAll()或String.split() 。 请改用诸如Apache Commons Lang之类的流行库来进行String操作。 3.不要使用iterator 现在此建议实际上并不适用于一般用例而仅适用于NOPE分支的深处。 但是您应该考虑一下。 编写Java-5样式的foreach循环很方便。 您可以完全忘记循环内部然后编写 for (String value : strings) {// Do something useful here
} 但是每次遇到此循环时如果strings是Iterable 则将创建一个新的Iterator实例。 如果您使用ArrayList 这将在堆上分配一个3 ints的对象 private class Itr implements IteratorE {int cursor;int lastRet -1;int expectedModCount modCount;// ... 相反您可以编写以下等效循环并仅在堆栈上“浪费”一个int值这非常便宜 int size strings.size();
for (int i 0; i size; i) {String value : strings.get(i);// Do something useful here
} …或者如果您的列表没有真正改变您甚至可以对其数组版本进行操作 for (String value : stringArray) {// Do something useful here
} 带走 从可写性和可读性以及从API设计的角度来看迭代器Iterable和foreach循环都非常有用。 但是它们为每次迭代在堆上创建一个小的新实例。 如果您多次运行此迭代则要确保避免创建此无用的实例而改为编写基于索引的迭代。 讨论区 关于上述部分的一些有趣的分歧特别是用索引访问代替Iterator用法 已经在Reddit上进行了讨论 。 4.不要调用该方法 有些方法简单昂贵。 在我们的NOPE分支示例中叶子上没有这样的方法但是您很可能有一个。 假设您的JDBC驱动程序需要经历难以置信的麻烦才能计算ResultSet.wasNull()的值。 您自己的SQL框架代码可能如下所示 if (type Integer.class) {result (T) wasNull(rs, Integer.valueOf(rs.getInt(index)));
}// And then...
static final T T wasNull(ResultSet rs, T value)
throws SQLException {return rs.wasNull() ? null : value;
} 每次您从结果集中获取一个int 此逻辑将立即调用ResultSet.wasNull() 。 但是getInt()合同显示为 返回列值 如果值为SQL NULL则返回值为0 因此对上述内容进行简单但可能很大的改进将是 static final T extends Number T wasNull(ResultSet rs, T value
)
throws SQLException {return (value null || (value.intValue() 0 rs.wasNull())) ? null : value;
} 因此这很容易 带走 不要在算法的“叶子节点”中调用昂贵的方法而要缓存调用或者在方法合同允许的情况下避免调用。 5.使用原语和堆栈 上面的示例来自jOOQ 它使用了很多泛型因此被迫对byte short int和long使用包装器类型-至少在泛型将在Java 10和项目Valhalla中实现特殊化之前。 但是您的代码中可能没有此约束因此应采取所有措施替换 // Goes to the heap
Integer i 817598; … 这样 // Stays on the stack
int i 817598; 使用数组时情况变得更糟 // Three heap objects!
Integer[] i { 1337, 424242 }; … 这样 // One heap object.
int[] i { 1337, 424242 }; 带走 当您深入了解NOPE分支时 应该非常警惕使用包装器类型。 可能会给GC带来很大压力必须时刻加油清理GC。 一种特别有用的优化可能是使用某种原始类型并为其创建大型的一维数组以及几个定界符变量以指示编码对象在数组上的确切位置。 trove4j是一个出色的原始集合库它比您的平均int[]要复杂一些 它随LGPL一起提供。 例外 此规则有一个例外 boolean和byte值很少因此无法完全由JDK缓存。 你可以写 Boolean a1 true; // ... syntax sugar for:
Boolean a2 Boolean.valueOf(true);Byte b1 (byte) 123; // ... syntax sugar for:
Byte b2 Byte.valueOf((byte) 123); 对于其他整数基元类型的低值包括char short int long 。 但是仅当您将它们自动装箱或调用TheType.valueOf() 才调用构造函数 除非确实需要新实例否则切勿在包装器类型上调用构造函数。 这个事实还可以帮助您为同事写一个复杂的愚蠢的愚人节玩笑 堆外 当然您可能还想尝试堆外库尽管它们更多是一个战略决策而不是本地优化。 彼得·劳瑞Peter Lawrey和本·科顿Ben Cotton撰写的有关该主题的有趣文章是 OpenJDK和HashMap…安全地教老狗新手超堆技巧 6.避免递归 像Scala这样的现代函数式编程语言鼓励使用递归因为它们提供了将尾递归算法优化回迭代算法的方法 。 如果您的语言支持这种优化则可能会很好。 但是即使如此算法的最细微改动也可能会产生一个分支从而阻止您的递归成为尾递归。 希望编译器能够检测到这一点 否则您可能会浪费大量的堆栈框架而这些内容可能只使用了几个局部变量就已经实现了。 带走 除了以下内容外没有什么要说的当您深入NOPE分支时始终喜欢迭代而不是递归。 7.使用entrySet 当您要遍历Map 同时需要键和值时必须有充分的理由编写以下内容 for (K key : map.keySet()) {V value : map.get(key);
} …而不是以下内容 for (EntryK, V entry : map.entrySet()) {K key entry.getKey();V value entry.getValue();
} 当您在NOPE分支中时 无论如何您都应该警惕地图因为很多O(1)地图访问操作仍然很多。 而且访问也不是免费的。 但是至少如果您不能没有地图请使用entrySet()对其进行迭代 无论如何都有Map.Entry实例您只需要访问它即可。 带走 在地图迭代期间同时需要键和值时请始终使用entrySet() 。 8.使用EnumSet或EnumMap 在某些情况下映射中可能的键数是预先已知的例如在使用配置映射时。 如果该数字相对较小则应真正考虑使用EnumSet或EnumMap 而不是常规的HashSet或HashMap 。 通过查看EnumMap.put()可以很容易地解释这一点 private transient Object[] vals;public V put(K key, V value) {// ...int index key.ordinal();vals[index] maskNull(value);// ...
} 此实现的本质是这样一个事实即我们拥有一个索引值数组而不是哈希表。 当插入一个新值时我们要查找映射项的所有工作就是询问枚举的常量序数该序数由Java编译器在每种枚举类型上生成。 如果这是一个全局配置映射即仅一个实例则提高的访问速度将帮助EnumMap大大优于HashMap 后者可能使用较少的堆内存但必须在每个键上运行hashCode()和equals() 。 带走 Enum和EnumMap是非常好的朋友。 每当您使用类似枚举的结构作为键时请考虑实际上使这些结构成为枚举并将其用作EnumMap键。 9.优化您的hashCode和equals方法 如果您不能使用EnumMap 至少要优化您的hashCode()和equals()方法。 一个好的hashCode()方法至关重要因为它将阻止对更昂贵的equals()进一步调用因为它将为每个实例集生成更多不同的哈希存储桶。 在每个类层次结构中您可能都有流行和简单的对象。 让我们看一下jOOQ的org.jooq.Table实现。 hashCode()的最简单最快的实现方法是 // AbstractTable, a common Table base implementation:Override
public int hashCode() {// [#1938] This is a much more efficient hashCode()// implementation compared to that of standard// QueryPartsreturn name.hashCode();
} …其中name只是表名。 我们甚至不考虑表的模式或任何其他属性因为表名通常在数据库中足够不同。 另外该name是一个字符串因此它内部已经有一个缓存的hashCode()值。 该注释很重要因为AbstractTable扩展了AbstractQueryPart 它是任何AST抽象语法树元素的通用基本实现。 通用AST元素没有任何属性因此它不能做任何假设来优化hashCode()实现。 因此重写的方法如下所示 // AbstractQueryPart, a common AST element
// base implementation:Override
public int hashCode() {// This is a working default implementation. // It should be overridden by concrete subclasses,// to improve performancereturn create().renderInlined(this).hashCode();
} 换句话说必须触发整个SQL呈现工作流以计算公共AST元素的哈希码。 equals()事情变得更加有趣 // AbstractTable, a common Table base implementation:Override
public boolean equals(Object that) {if (this that) {return true;}// [#2144] Non-equality can be decided early, // without executing the rather expensive// implementation of AbstractQueryPart.equals()if (that instanceof AbstractTable) {if (StringUtils.equals(name, (((AbstractTable?) that).name))) {return super.equals(that);}return false;}return false;
} 第一件事 总是 不仅在NOPE分支中 提前中止每个equals()方法如果 this argument this incompatible type argument 请注意如果您使用instanceof检查兼容类型则后一个条件包括argument null 。 之前我们在“编码Java的10个微妙的最佳实践”中已经对此进行了博客撰写。 现在在明显的情况下尽早中止比较之后您可能还想在可以做出部分决策时就中止比较。 例如jOOQ的Table.equals()的Table.equals()是要使两个表相等无论具体的实现类型如何它们都必须具有相同的名称。 例如这两个项目不可能相等 com.example.generated.Tables.MY_TABLE DSL.tableByName(MY_OTHER_TABLE) 如果argument 不能等于this 并且如果我们可以轻松地进行检查那么我们可以进行检查如果检查失败则中止。 如果检查成功我们仍然可以从super进行更昂贵的实现。 鉴于Universe中的大多数对象不相等我们将通过简化此方法来节省大量CPU时间。 有些对象比其他对象更平等 对于jOOQ大多数实例实际上是由jOOQ源代码生成器生成的表该表的equals()实现甚至得到了进一步优化。 其他数十种表类型派生表表值函数数组表联接表数据透视表公用表表达式等可以保持其“简单”实现。 10.集合思考而不是个别思考 最后但并非最不重要的一点是它与Java不相关但适用于任何语言。 此外我们将离开NOPE分支因为此建议可能只是帮助您从O(N 3 )迁移到O(n log n)或类似的东西。 不幸的是许多程序员认为是简单的本地算法。 他们一步一步地解决问题逐分支逐循环逐方法。 那就是命令式和/或函数式编程风格。 从纯命令式到面向对象仍然命令式再到函数式编程时为“更大的画面”建模变得越来越容易但是所有这些样式都缺少只有SQLR和类似语言才能具备的功能 声明式编程。 在SQL中 并且我们很喜欢因为它是jOOQ博客 您可以声明要从数据库中获取的结果而不会产生任何算法含义。 然后数据库可以考虑所有可用的元数据 例如约束键索引等 以找出可能的最佳算法。 从理论上讲从一开始这就是SQL和关系演算背后的主要思想。 在实践中SQL供应商仅在最近十年才实施了高效的CBO基于成本的优化工具 因此请与我们保持在一起直到2010年SQL最终释放出其全部潜力大约是时间。 但是您不必执行SQL即可进行集合思考。 集合/收藏/袋子/清单可提供所有语言和库。 使用集合的主要优点是您的算法将变得更加简洁。 编写起来非常容易 SomeSet INTERSECT SomeOtherSet 而不是 // Pre-Java 8
Set result new HashSet();
for (Object candidate : someSet)if (someOtherSet.contains(candidate))result.add(candidate);// Even Java 8 doesnt really help
someSet.stream().filter(someOtherSet::contains).collect(Collectors.toSet()); 有人可能会认为函数式编程和Java 8将帮助您编写更简单更简洁的算法。 不一定是真的。 您可以将命令性的Java-7循环转换为功能性的Java-8 Stream集合但是您仍在编写完全相同的算法。 编写类似SQL的表达式是不同的。 这个… SomeSet INTERSECT SomeOtherSet …可以由实施引擎以1000种方式实施。 正如我们今天所了解的那样在运行INTERSECT操作之前将两个集合自动转换为EnumSet也许是明智的。 也许我们可以并行化此INTERSECT而无需对Stream.parallel()进行低级调用。 结论 在本文中我们讨论了在NOPE分支上完成的优化即深入到高复杂度算法中。 在我们的案例中作为jOOQ开发人员我们对优化我们的SQL生成感兴趣 每个查询仅在单个StringBuilder上生成 我们的模板引擎实际上是解析字符而不是使用正则表达式 我们会尽可能使用数组尤其是在侦听器上进行迭代时 我们避免了不必调用的JDBC方法 等等… jOOQ位于“食物链的底部”因为它是次API在调用离开JVM进入DBMS之前我们的客户应用程序正在调用它。 位于食物链的底部意味着在jOOQ中执行的每一行代码都可能被称为N x O x P倍因此我们必须热切地进行优化。 您的业务逻辑不在NOPE分支中 。 但是您自己的本地基础结构逻辑可能是自定义SQL框架自定义库等应该根据我们今天所看到的规则进行审查。 例如使用Java Mission Control或任何其他探查器。 翻译自: https://www.javacodegeeks.com/2015/02/top-10-easy-performance-optimisations-java.html