我的个人提示:
- 首先检查您的算法 - 当它真的应该是O(n.log n)时,您是否会产生O(n ^ 2)成本?如果你选择了一个糟糕的算法,剩下的调整就是浪费时间。
- 注意常见的“陷阱”,例如遍历列表/序列的O(n)成本。
- 利用Clojure中的优秀功能,例如复制大型持久数据结构的O(1)成本或映射/设置/矢量访问的O(log32 n)成本。
- 明智地选择Clojure的核心结构:
- 当您需要一些可变数据时,原子非常棒,例如更新循环中的一些数据
- 如果要按顺序遍历某些数据,请使用列表而不是矢量或地图,因为这样可以避免在遍历序列时创建临时对象。
- 在适当的地方使用 deftype / defrecord / defprotocol 。这些都经过了大量优化,特别是从Clojure 1.2开始,应该优先考虑defstruct / multimethods。
- 利用Clojure的并发功能:
- pmap 和未来是您在同时进行多项独立计算时利用多核的相对简单的方法。
- 请记住,由于Clojure的不可变持久数据结构,制作和处理多个数据副本非常便宜。拍摄快照时你也不必担心锁定.....
- 如果您正在使用Java代码,请使用“(set!* warn-on-reflection * true)”和消除每个反射警告。反射是最昂贵的操作之一,如果反复进行,反射会真的减慢你的应用程序。
- 如果您仍需要更高性能,请确定代码中性能最关键的部分(例如,应用程序花费90%以上CPU时间的5%行),详细分析此部分并明智地应用以下规则:
- 避免懒惰。懒惰是一个很好的功能,但带来一些额外的开销。请注意,许多Clojure的常见序列/列表函数都是惰性的(例如,for,map,partition)。 loop / recur,dotimes和reduce是你不懒的朋友。
- 使用原始提示和未选中的算术可以更快地生成算术/数字代码。与Clojure的默认BigInteger算法相比,基元 更快
。
- 最小化内存分配 - 尽量避免创建太多不必要的中间数据(向量,列表,映射,非原始数字等)。所有分配都会产生少量的额外开销,并且随着时间的推移会导致更长/更长的GC暂停(如果您正在编写游戏/软实时应用程序,这可能是一个更大的问题。)。
- (Ab)使用Java数组 - 在Clojure中并不是真正的惯用语,但是aget / aset / areduce和朋友非常快(他们受益于很多JVM优化!!)。 (Ab)使用原始数组获得额外奖励积分。
- 使用宏 - 在可能的情况下在编译时生成丑陋但快速的代码
完成上述所有操作后,Clojure代码的性能会非常好 - 通过仔细调整,我通常能够合理地接近纯Java性能,这对于动态语言来说非常令人印象深刻!
您可以使用JVisualVM对Clojure代码进行分析(有关示例,请参阅JVisualVM and Clojure)。这至少应该指向慢速代码的正确方向。