Java:为什么数组(w索引)*数量级*比映射(键)访问速度快?
我们进行了附加测试。结果一致表明,通过索引数组访问比通过键映射访问快10倍。这个数量级的差异令我们惊讶 地图的键是java.lang.String。。。计算映射键的java.lang.String.hashcode()实现的成本是唯一的原因吗?在所附的代码中,我只使用一个键进行了练习Java:为什么数组(w索引)*数量级*比映射(键)访问速度快?,java,Java,我们进行了附加测试。结果一致表明,通过索引数组访问比通过键映射访问快10倍。这个数量级的差异令我们惊讶 地图的键是java.lang.String。。。计算映射键的java.lang.String.hashcode()实现的成本是唯一的原因吗?在所附的代码中,我只使用一个键进行了练习 java.lang.String key = 1; 在这种情况下,编译器/运行时不缓存吗?还是每次调用都会重新计算 谢谢你的见解 public class PerfTest { static java.util
java.lang.String key = 1;
在这种情况下,编译器/运行时不缓存吗?还是每次调用都会重新计算
谢谢你的见解
public class PerfTest {
static java.util.HashMap<String, Double> map;
static Double[] array = {1.0,2.0,3.0,4.0,5.0,6.0,7.0,8.0,9.0,10.0};
static long nTimes = 1000000;
static{
map = new java.util.HashMap<String, Double>();
map.put("1", new Double(1));
map.put("2", new Double(2));
map.put("3", new Double(3));
map.put("4", new Double(4));
map.put("5", new Double(5));
map.put("6", new Double(6));
map.put("7", new Double(7));
map.put("8", new Double(8));
map.put("9", new Double(9));
map.put("10", new Double(10));
}
public static void main(String[] args){
PerfTest tester = new PerfTest();
long timeInMap = tester.testHashMap();
long timeInArray = tester.testArray();
System.out.println("Corrected time elapsed in map(in seconds): "
+ (timeInMap)/1000000000.0);
System.out.println("Corrected time elapsed in array(in seconds): "
+ (timeInArray)/1000000000.0);
}
private long testHashMap(){
int sz = map.size();
long startTime = System.nanoTime();
String key = "1";
for (int i=0; i <nTimes; i++){
double sum = 0;
for (int j =1; j<=sz; j++){
sum += map.get(key);
}
}
return (System.nanoTime() - startTime);
}
private long testArray(){
long startTime = System.nanoTime();
for (int i=0; i <nTimes; i++){
double sum = 0;
for (int j=0; j< array.length; j++) {
sum += array[j];
}
}
return (System.nanoTime() - startTime);
}
}
公共类性能测试{
静态java.util.HashMap映射;
静态双[]数组={1.0,2.0,3.0,4.0,5.0,6.0,7.0,8.0,9.0,10.0};
静态长时间=1000000;
静止的{
map=newjava.util.HashMap();
地图放置(“1”,新双(1));
地图放置(“2”,新双(2));
地图放置(“3”,新双(3));
地图放置(“4”,新双(4));
地图放置(“5”,新双(5));
地图放置(“6”,新双(6));
地图放置(“7”,新双(7));
地图放置(“8”,新双(8));
地图放置(“9”,新双(9));
地图放置(“10”,新双(10));
}
公共静态void main(字符串[]args){
PerfTest tester=新的PerfTest();
long-timeInMap=tester.testHashMap();
long-timeInArray=tester.testArray();
System.out.println(“映射中经过的校正时间(秒):”
+(timeInMap)/100000000.0);
System.out.println(“数组中经过的校正时间(秒):”
+(timeInArray)/100000000.0);
}
私有长testHashMap(){
int sz=map.size();
long startTime=System.nanoTime();
String key=“1”;
对于(int i=0;i是),这里的费用是计算密钥
如果您知道索引,那么就没有理由使用像HashMap这样的复杂数据结构来代替普通数组
当密钥未知且基于对象的内容时,您可能希望使用HashMap。因此,更有效的示例是从要查找的对象开始,在数组中搜索它,而不是知道它在哪里,因为HashMap就是这样做的。是的,这里的费用是计算密钥
如果您知道索引,那么就没有理由使用像HashMap这样的复杂数据结构来代替普通数组
当密钥未知且基于对象的内容时,您可能希望使用哈希映射。因此,更有效的示例是从要查找的对象开始,在数组中搜索它,而不是知道它在哪里,因为哈希映射就是这样做的。如果您在cHashMap实际上是一个哈希表,它将数据分散在底层数组中,必须计算索引,在数组中查找并返回。另一方面,数组是一个连续的内存块,在查找索引位置时不需要计算
此外,您访问阵列的顺序是非常可预测的,因此预取内存,就像所有现代处理器一样,不会导致任何未命中。如果您了解到隐藏的哈希映射实际上是一个哈希表,它将您的数据分散到底层阵列上,并且必须计算在数组中查找索引并将其交回。另一方面,数组是一个连续的内存块,在查找索引位置时不需要计算
此外,您访问阵列的顺序是非常可预测的,因此预取内存,就像所有现代处理器一样,不会导致任何未命中。使用Java的系统时间并不是获得真正基准的好方法。我重构了您的代码以供使用(这会使JVM升温,以及其他事情)…并发现了与您类似的结果。评论员正确地指出,我的原始版本不好,大部分时间都将调用System.out.println
正如我所说,编写基准测试很难。下面更新的是新的、正确的版本
结果:
代码:
import com.google.caliper.Runner;
导入com.google.caliper.SimpleBenchmark;
公共类性能测试{
公共静态双hashNum=0;
公共静态双数组数=0;
公共静态类PerfBenchmark扩展了SimpleBenchmark{
静态java.util.HashMap映射;
静态双[]数组={1.0,2.0,3.0,4.0,5.0,6.0,7.0,8.0,9.0,10.0};
静止的{
map=newjava.util.HashMap();
地图放置(“1”,新双(1));
地图放置(“2”,新双(2));
地图放置(“3”,新双(3));
地图放置(“4”,新双(4));
地图放置(“5”,新双(5));
地图放置(“6”,新双(6));
地图放置(“7”,新双(7));
地图放置(“8”,新双(8));
地图放置(“9”,新双(9));
地图放置(“10”,新双(10));
}
公共无效时间哈希映射(整数次){
int sz=map.size();
String key=“1”;
对于(inti=0;i使用Java的系统时间不是获得真正基准的好方法。我重构了您的代码以供使用(这会使JVM升温,等等)…并发现了与您类似的结果。评论员正确地指出,我的原始版本不好,大部分时间都将调用System.out.println
正如我所说,编写基准测试很难。下面更新的是新的、正确的版本
结果:
代码:
import com.google.caliper.Runner;
导入com.google.caliper.SimpleBenchmark;
公共类性能测试{
公共静态双hashNum=0;
公共静态双数组数=0;
公共静态类PerfBenchmark扩展了SimpleBenchmark{
静态java.util.HashMap映射;
静态双[]数组={1.0,2.0,3.0,4.0,5.0,6.0,7.0,8.0,9.0,10.0};
静止的{
map=n
0% Scenario{vm=java, trial=0, benchmark=HashMap} 51.04 ns; σ=0.22 ns @ 3 trials
50% Scenario{vm=java, trial=0, benchmark=Array} 4.05 ns; σ=0.01 ns @ 3 trials
benchmark ns linear runtime
HashMap 51.04 ==============================
Array 4.05 ==
import com.google.caliper.Runner;
import com.google.caliper.SimpleBenchmark;
public class PerfTest {
public static double hashNum = 0;
public static double arrayNum = 0;
public static class PerfBenchmark extends SimpleBenchmark {
static java.util.HashMap<String, Double> map;
static Double[] array = {1.0,2.0,3.0,4.0,5.0,6.0,7.0,8.0,9.0,10.0};
static{
map = new java.util.HashMap<String, Double>();
map.put("1", new Double(1));
map.put("2", new Double(2));
map.put("3", new Double(3));
map.put("4", new Double(4));
map.put("5", new Double(5));
map.put("6", new Double(6));
map.put("7", new Double(7));
map.put("8", new Double(8));
map.put("9", new Double(9));
map.put("10", new Double(10));
}
public void timeHashMap(int nTimes){
int sz = map.size();
String key = "1";
for (int i=0; i <nTimes; i++){
double sum = 0;
for (int j =1; j<=sz; j++){
sum += map.get(key);
}
hashNum += sum;
}
}
public void timeArray(int nTimes){
for (int i=0; i <nTimes; i++){
double sum = 0;
for (int j=0; j< array.length; j++) {
sum += array[j];
}
arrayNum += sum;
}
}
}
public static void main(String[] args){
Runner.main(PerfBenchmark.class, new String[0]);
System.out.println(hashNum);
System.out.println(arrayNum);
}
}
for (Entry<K,V> e = table[indexFor(hash, table.length)]; e != null; e = e.next) {
Object k;
if (e.hash == hash && ((k = e.key) == key || key.equals(k)))
return e.value;
}
public abstract class Benchmark {
final String name;
public Benchmark(String name) {
this.name = name;
}
abstract int run(int iterations) throws Throwable;
private BigDecimal time() {
try {
int nextI = 1;
int i;
long duration;
do {
i = nextI;
long start = System.nanoTime();
run(i);
duration = System.nanoTime() - start;
nextI = (i << 1) | 1;
} while (duration < 100000000 && nextI > 0);
return new BigDecimal((duration) * 1000 / i).movePointLeft(3);
} catch (Throwable e) {
throw new RuntimeException(e);
}
}
@Override
public String toString() {
return name + "\t" + time() + " ns";
}
public static void main(String[] args) throws Exception {
Benchmark[] benchmarks = {
new Benchmark("array lookup") {
Double[] array = { 1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0, 10.0 };
@Override
int run(int iterations) throws Throwable {
double sum = 0;
for (int i = 0; i < iterations; i++) {
for (int j = 0; j < array.length; j++) {
sum += array[j];
}
}
return (int) sum;
}
}, new Benchmark("map lookup") {
Map<String, Double> map = new HashMap<>();
{
map.put("1", new Double(1));
map.put("2", new Double(2));
map.put("3", new Double(3));
map.put("4", new Double(4));
map.put("5", new Double(5));
map.put("6", new Double(6));
map.put("7", new Double(7));
map.put("8", new Double(8));
map.put("9", new Double(9));
map.put("10", new Double(10));
}
@Override int run(int iterations) throws Throwable {
String key = "1";
double sum = 0;
for (int i=0; i <iterations; i++){
for (int j =1; j<=map.size(); j++){
sum += map.get(key);
}
}
return (int) sum;
}
}
};
for (Benchmark bm : benchmarks) {
System.out.println(bm);
}
}
}
array lookup 15.250 ns
map lookup 124.946 ns
}, new Benchmark("array lookup with printing") {
Double[] array = { 1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0, 10.0 };
@Override
int run(int iterations) throws Throwable {
for (int i = 0; i < iterations; i++) {
double sum = 0;
for (int j = 0; j < array.length; j++) {
sum += array[j];
}
System.out.println(sum);
}
return 0;
}
}, new Benchmark("map lookup with printing") {
Map<String, Double> map = new HashMap<>();
{
map.put("1", new Double(1));
map.put("2", new Double(2));
map.put("3", new Double(3));
map.put("4", new Double(4));
map.put("5", new Double(5));
map.put("6", new Double(6));
map.put("7", new Double(7));
map.put("8", new Double(8));
map.put("9", new Double(9));
map.put("10", new Double(10));
}
@Override int run(int iterations) throws Throwable {
String key = "1";
for (int i=0; i <iterations; i++){
double sum = 0;
for (int j =1; j<=map.size(); j++){
sum += map.get(key);
}
System.out.println(sum);
}
return 0;
}
}
array lookup with printing 43301.251 ns
map lookup with printing 18330.935 ns
public V get(Object key) {
if (key == null)
return getForNullKey();
Entry<K,V> entry = getEntry(key);
return null == entry ? null : entry.getValue();
}
final Entry<K,V> getEntry(Object key) {
int hash = (key == null) ? 0 : hash(key);
for (Entry<K,V> e = table[indexFor(hash, table.length)];
e != null;
e = e.next) {
Object k;
if (e.hash == hash &&
((k = e.key) == key || (key != null && key.equals(k))))
return e;
}
return null;
}
final int hash(Object k) {
int h = 0;
if (useAltHashing) {
if (k instanceof String) {
return sun.misc.Hashing.stringHash32((String) k);
}
h = hashSeed;
}
h ^= k.hashCode();
// This function ensures that hashCodes that differ only by
// constant multiples at each bit position have a bounded
// number of collisions (approximately 8 at default load factor).
h ^= (h >>> 20) ^ (h >>> 12);
return h ^ (h >>> 7) ^ (h >>> 4);
}
static int indexFor(int h, int length) {
return h & (length-1);
}
// from String.class
public int hashCode() {
int h = hash;
if (h == 0 && value.length > 0) {
char val[] = value;
for (int i = 0; i < value.length; i++) {
h = 31 * h + val[i];
}
hash = h;
}
return h;
}
public boolean equals(Object anObject) {
if (this == anObject) {
return true;
}
...
}