我正在编写一个将数字(长)转换为一小组结果对象的java应用程序。这种映射过程对于应用程序的性能非常关键,因为它经常需要。
public static Object computeResult(long input) {
Object result;
// ... calculate
return result;
}
大约有150,000,000个不同的关键对象,以及大约3,000个不同的值。 从输入数(长)到输出(不可变对象)的转换可以通过我的算法以每秒4,000,000次转换的速度计算。 (使用4个线程)
我想缓存150M不同可能输入的映射,以使转换更快,但我发现创建这样的缓存有些困难:
public class Cache {
private static long[] sortedInputs; // 150M length
private static Object[] results; // 150M length
public static Object lookupCachedResult(long input) {
int index = Arrays.binarySearch(sortedInputs, input);
return results[index];
}
}
我试图创建两个长度为150M的数组。第一个数组包含所有可能的输入long,并按数字排序。第二个数组保存对与第一个数组输入对应的索引处的3000个不同的,预先计算的结果对象之一的引用。
要获得缓存结果,我会对第一个数组上的输入数字进行二进制搜索。然后在同一索引的第二个数组中查找缓存的结果。
遗憾的是,这种缓存方法并不比计算结果快。甚至不到一半,每秒只有大约1.5M的查找次数。 (也使用4个线程)有人会想到在这种情况下更快地缓存结果吗?
我怀疑有一个数据库引擎能够每秒回答超过4,000,000个查询,比如一个普通的工作站。
答案 0 :(得分:1)
Hashing是这里的方法,但是我会避免使用HashMap,因为它只适用于对象,即每次插入long时都必须构建一个Long,这会减慢它的速度。由于JIT,这个性能问题可能并不重要,但我建议至少尝试以下方法并根据HashMap变体测量性能:
将你的长片保存在一个长度为n的长阵列中> 3000并通过一个非常简单的哈希函数手动进行散列(因此效率很高)
index = key % n
。由于您事先知道了3000个可能的值,因此您可以凭经验找到数组长度n
,这样这个简单的哈希函数不会导致冲突。所以你绕过了重复等等并且具有真正的O(1) - 性能。
其次,我建议你看看像
这样的Java数值库两者都有本地Lapack和BLAS实现的支持,这些实现通常由非常聪明的人进行高度优化。也许你可以用矩阵/向量代数来表示你的算法,这样它就可以一次计算整个长数组(或者大块数)。
答案 1 :(得分:1)
大约有150,000,000个不同的关键对象,以及大约3,000个不同的值。
使用少量值,您应该确保它们被重复使用(除非它们是非常小的对象)。为此,Interner是完美的(虽然你可以自己运行)。
我尝试了hashmap和treemap,两次尝试都以outOfMemoryError结束。
这两者都有巨大的内存开销。使用TreeMap
并没有多大意义,因为它使用了您已经尝试过的二进制搜索。
至少有三种可用于长对象地图的实现,google用于“原始集合”。这应该使用比两个数组稍多的内存。哈希通常是O(1)
(让我们忽略最糟糕的情况,因为它没有理由发生,是吗?)和更好的内存局部性,它会将你的二进制搜索击败(*)20倍。您的二进制搜索需要log2(150e6)
,即大约27个步骤,并且平均可能需要两个哈希。这取决于您打包哈希表的紧密程度;这通常是创建时给出的参数。
如果您运行自己的(您很可能不应该),我建议使用大小为1 << 28
的数组,即268435456条目,以便您可以使用按位运算进行索引。
(*)这样的预测很难,但我确信这值得尝试。