我的代码要求我在每次调用函数时创建一个大的(301x301x19 项)ArrayList,它有一些初始值(有些是 0,有些是 1,等等)。起始值总是相同的,每次调用函数时都需要将其加载到数组中,以便函数有自己的这些初始值副本来处理。
最初我在每次调用该函数时都重新计算数组,但事实证明那慢得可笑;相反,我现在只计算一次初始数组,并在每次调用该函数时制作它的本地副本(这样我就可以在不更改初始数组值的情况下更改值)。
但是,事实证明,复制数组的速度仍然非常慢(超过 3/4 的计算时间都花在了复制这个数组上)。我尝试了以下方法:
// oldList is an ArrayList<Byte>
ArrayList<Byte> newList = new ArrayList<Byte>(oldList);
// oldList is an ArrayList<Byte>
ArrayList<Byte> newList = new ArrayList<Byte>();
newList.addAll(oldList);
// oldList is a Byte[]
ArrayList<Byte> newList = new ArrayList<Byte>(Arrays.asList(oldList));
所有这些方法对我的应用来说都太慢了;有没有更快的技术来做到这一点,还是我运气不好?
最佳答案
总结:
- 旨在设计出复制如此多大型数据结构的需求(我知道这是一个难题)
- 避免指针追逐,使用数组而不是 ArrayLists。如果您的对象包含其他对象,请尝试用基元替换它们。这里的最终目的是减少到一个原始数组,例如一个字节数组
- 压缩你的数据结构,使用数组,更小的类型;目标是通过复制更少的实际字节获得相同数量的好处
- 使用System.arrayCopy
- 如果您仍然想走得更快,那么将内存布局和责任从 JVM 中移开并直接使用 sun.misc.Unsafe(也称为“用剪刀运行”)
更改为更容易复制的数据结构,并使用 System.arraycopy 将与您在问题中概述的方法一样快。
System.arraycopy 作为 native 调用实现。大多数 JVM 供应商都会准备一个原生版本,使用原生指令来加速内存复制。
不幸的是,复制大内存区域会在 JVM 中产生意想不到的副作用,主要是围绕垃圾收集器。
- 在内存复制期间,JVM 无法访问安全点,这会阻止 STW GC 启动
- GC 未启动会导致注意安全点的线程等待更长时间,从而在与此工作无关的线程上造成奇怪的停顿
- 大数组可能无法容纳在 TAB(用于加速对象分配的线程本地缓冲区)中,这意味着对象分配在进入特殊情况代码时会减慢
大对象会增加 GC 周期中过早使用的可能性,这会增加更昂贵的老一代/完整 GC 的频率(与更便宜的年轻一代 GC 相对)
- 注意:要看到上述效果,我们必须谈论非常高的分配率和丢弃率。大多数在这里和那里进行一些分配和复制的算法不会看到这些问题;现代 JVM 甚至可以处理相当高的速率,这些问题不会发生,直到超过阈值并且我们一直在杆子上旋转的盘子开始落到地板上。
关于Java:制作初始 ArrayList 的本地副本的最快方法?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/26003767/