我使用 HashMap 缓存通过递归算法计算的大约 200 万个值。我使用HashMap<Integer, Double>
Collections Framework 或TIntDoubleHashMap
Trove 库中的一个,由boolean useTrove
变量控制,如下面的代码所示。
我确实希望 Trove 库更快,因为它避免了自动装箱等。事实上,put()
和get()
调用需要大约 300 毫秒来运行(总共),THashMap
而HashMap<>
.
现在,我的整体程序运行时间约为 2.8 秒,使用THashMap
6.7 秒HashMap<>
。这种差异不能仅用put()
andget()
调用增加的运行时间来解释。
我怀疑这种大幅增加的运行时间
HashMap<>
是由于这种实现的内存效率很低,因为每个 int/double 都需要装箱到一个对象中,而这种增加的内存使用会导致程序其他部分的缓存未命中。这种解释是否有意义,我如何确认/拒绝这个假设?一般来说,我如何探索此类场景的算法优化?分析算法并不能轻易指出
HashMap<>
是罪魁祸首,至少如果仅考虑 CPU 时间的话。这仅仅是提前知道内存使用需要优先考虑内存需求的问题吗?
完整代码如下。
import java.util.HashMap;
import gnu.trove.map.hash.TIntDoubleHashMap;
class RuntimeStopWatch {
long elapsedTime;
long startTime;
RuntimeStopWatch() { reset(); }
void reset() { elapsedTime = 0; }
void start() { startTime = System.nanoTime(); }
void stop() {
long endTime = System.nanoTime();
elapsedTime += (endTime - startTime);
startTime = endTime;
}
void printElapsedTime(String prefix) {
System.out.format(prefix + "%dms\n", elapsedTime / 1000000);
}
}
public class HashMapBehaviour {
static RuntimeStopWatch programTime = new RuntimeStopWatch();
static RuntimeStopWatch hashMapTime = new RuntimeStopWatch();
static HashMap<Integer, Double> javaHashMapCache;
static TIntDoubleHashMap troveHashMapCache;
static boolean useTrove;
public static void main(String[] args) {
// useTrove = true;
useTrove = false;
javaHashMapCache = new HashMap<>();
troveHashMapCache = new TIntDoubleHashMap();
programTime.start();
recursiveFunction(29, 29, 178956970);
programTime.stop();
programTime.printElapsedTime("Program: ");
hashMapTime.printElapsedTime("Hashmap: ");
}
static double recursiveFunction(int n, int k, int bitString) {
if (k == 0) return 0.0;
if (useTrove) {
hashMapTime.start();
if (troveHashMapCache.containsKey(bitString | (1 << n))) return troveHashMapCache.get(bitString | (1 << n));
hashMapTime.stop();
} else {
hashMapTime.start();
if (javaHashMapCache.containsKey(bitString | (1 << n))) return javaHashMapCache.get(bitString | (1 << n));
hashMapTime.stop();
}
double result = 0.0;
for (int i = 0; i < (n >> 1); i++) {
double play1 = recursiveFunction(n - 1, k - 1, stripSingleBit(bitString, i));
double play2 = recursiveFunction(n - 1, k - 1, stripSingleBit(bitString, n - i - 1));
result += Math.max(play1, play2);
}
if (useTrove) {
hashMapTime.start();
troveHashMapCache.put(bitString | (1 << n), result);
hashMapTime.stop();
} else {
hashMapTime.start();
javaHashMapCache.put(bitString | (1 << n), result);
hashMapTime.stop();
}
return result;
}
static int stripSingleBit(int bitString, int bitIndex) {
return ((bitString >> (bitIndex + 1)) << bitIndex) | (bitString & ((1 << bitIndex) - 1));
}
}