map iterator
Java Map性能有很多方面可以衡量,但是关键的一个是简单的单线程扫描。 这是一些针对Iterators和Java 8 Map.forEach()
简单测试代码,以及一些图形结果。
1.性能测试困难
性能测试是一项非常困难的工作,精确的可重复性测试需要Java Microbenchmarking Harness之类的框架来过滤掉许多噪声源并提供诸如置信区间的统计信息。 但是,这里简单测试代码的结果是相对可重复的,并且与作者在JDK Maps和AirConcurrentMap(在boilerbay.com和githuboilbay / airconcurrentmap )上进行的JMH测试很好地相关。 还有针对流等的更广泛的JMH测试。
JMH并不复杂,但是它需要将工具从IDE切换到Maven模型,学习控件注释集,并处理充满混乱的另一个目录树以及可能的新IDE项目。 另外,我们希望一次运行即可自动跨越一系列地图大小和一组地图:这需要JMH中的“参数化”注释。 该测试的输出数据格式可以轻松地根据所需绘图工具的需求进行调整。
1.1引起噪音的问题
要获得良好的测试,必须解决许多问题:
- 内部循环不得有大量开销。 这可以通过使它简单地监视共享静态volatile int变量(由“检测信号”线程递增)的变化来解决。
- 垃圾收集可能会增加。这可以通过在针对不同Map的测试中分配GC开销循环机制来帮助。 我们还避免创建任何无法访问的对象。 在ORACLE JVM中使用-Xloggc:<file>来观看GC;
- 在运行期间,JVM可以随时优化代码。 我们使用每个测试的预热运行来允许这种情况发生。 很难知道何时会确切发生,但这仅需几秒钟。 可以通过更长的预热将其最小化。 典型的JVM标志'-XX:+ PrintCompilation'显示了优化进度。 另请参阅ORACLE文章 ;
- 随着Maps的增长,JVM会不稳定地增长,因此增长后的测试必须预热。
- 不同的Map使用不同的内存量,因此即使为每个测试分叉JVM也将取决于JVM的增长特征。 我们将所有Map保留在一个JVM中;
- 不同的地图必须具有完全相同的内容。 我们使用由Map大小作为种子的同步随机数生成器;
- 大型地图需要更多的时间进行迭代,而不是较小的时间。 心跳允许运行更多较小的Map,因此每次测试都需要固定的时间。
- 地图不得共享内容,因此早期测试不会将数据带入可用于后续测试的CPU缓存中;
- “及时”编译器不能优化循环。 我们打印内容的总和,这样循环就可以得到明显的结果。 如果要使用循环计数器,则JIT可以识别出模式并更改循环以增加一个大于1的数字,从而跳过许多迭代! 是的,这发生了! (至少在AirConcurrentMap中)。
2.结果
这些预防措施可帮助我们得出一些初步但可重复的结论。 结果显示:
- forEach()比Iterators快得多;
- 在大约3万个条目以下,ConcurrentSkipListMap的迭代速度最快,而在上面,AirConcurrentMap的迭代速度最快;
- ConcurrentSkipListMap forEach()在大约100个条目以下最快,而AirConcurrentMap在上面最快。
3.代码详细信息
测试代码使用Test
类包装器来包含特定的Map以及测试进度状态,例如时间,map大小,循环和运行总计。 该组织希望使main()更具可读性。
Test类实现BiConsumer
以便可以在forEach(this)
调用中使用它。 它只是覆盖了accept(Integer, Integer)
。 我们还测试了forEach((k, v) -> total += v)
(作者尝试过,结果大致相同)。 无论哪种情况,都不能将局部变量用作总累加器,因为局部变量必须“有效地”终止于内部类或lambda,因此无论如何我们都需要一个包含范围内的实例变量,例如Test
那个。 使用流进行简化也可以,但是我们对Iterators感兴趣。 (有关/ jmh中的相关流测试,请参见https://github.com/boilerbay/airconcurrentmap )。
心跳是一个static int
,每秒由一个单独的Thread递增一次。 心跳必须是易失的,以便其更改在线程之间传播–否则程序永远不会终止。
USE_ITERATION
和USE_LAMBDA
标志是static final
,因此javac实际上会提前评估它影响的代码,从而USE_LAMBDA
代码。 这被定义为必需的行为,因此那里没有开销。 当然,您必须重新编译才能进行各种测试,只是不要将它们更改为非静态或非最终的!
test.testIterator()
的动态方法调用不会减慢内部循环,因为总是内联长度为35个字节或更少的方法。 而且,这里没有多态性Test
类没有扩展,因此没有用于分发的vtable,也没有要压入堆栈的参数。
测试是相对可重复的,并在图中显示了总体模式。
4.代码
public class JavaCodeGeeksIteratorArticle {
static volatile int heartBeat = 0;
// otherwise use forEach()
static final boolean USE_ITERATION = false;
static final boolean USE_LAMBDA = true;
public static void main(String... args) {
System.out.println((USE_ITERATION ? "Iterator" :
USE_LAMBDA ? " forEach(lambda)" : "ForEach()") + " performance test");
Test tests[] = {
new Test(new HashMap<Integer, Integer>()),
new Test(new TreeMap<Integer, Integer>()),
new Test(new ConcurrentHashMap<Integer, Integer>()),
new Test(new ConcurrentSkipListMap<Integer, Integer>()),
new Test(new AirConcurrentMap<Integer, Integer>())
};
int sizes[] = new int[] {
1, 3, 10, 30, 100, 300, 1000, 3000, 10_000, 30_000, 100_000, 300_000,
1000_000, 3_000_000, 10_000_000
};
// Just increment heartBeat every so often. It is volatile.
// Reading it is very fast.
new Thread(new Runnable() {
public void run() {
while (true) {
heartBeat++;
try {
Thread.sleep(100);
} catch (InterruptedException e) {
}
}
}
}).start();
for (int i = 0; i < sizes.length; i++) {
for (Test test : tests)
test.fillTo(sizes[i]);
// warmup
for (Test test : tests) {
int nextHeartBeat = heartBeat + 20;
while (heartBeat < nextHeartBeat)
if (USE_ITERATION)
test.testIterator();
else if (USE_LAMBDA)
test.testForEachLambda();
else
test.testForEach();
}
for (Test test : tests) {
test.time = 0;
test.loops = 0;
long t0 = System.nanoTime();
int nextHeartBeat = heartBeat + 30;
while (heartBeat < nextHeartBeat) {
if (USE_ITERATION)
test.testIterator();
else if (USE_LAMBDA)
test.testForEachLambda();
else
test.testForEach();
}
long t1 = System.nanoTime();
test.time += (t1 - t0);
}
for (Test test : tests)
test.printResult();
// System.out.println("---------------");
}
}
}
class Test implements BiConsumer<Integer, Integer> {
// The total provides a tangible result to prevent optimizing-out
long total = 0;
long time = 0;
int size = 0;
long loops = 1;
Map<Integer, Integer> map;
Test(Map<Integer, Integer> map) {
this.map = map;
}
void fillTo(int newSize) {
Random random = new Random(size);
while (size < newSize) {
Integer n = new Integer(random.nextInt());
map.put(n, n);
size++;
}
}
void testIterator() {
for (Integer v : map.values()) {
total += v.intValue();
} loops++;
}
// This has the same effect and is 'terser'
void testForEachLambda() {
map.forEach((k, v) -> total += v);
loops++;
}
void testForEach() {
map.forEach(this);
loops++;
}
// Implement BiConsumer for forEach()
@Override
public void accept(Integer k, Integer v) {
total += k.intValue();
}
void printResult() {
double seconds = time / 1e9;
System.out.printf("%22s size=% 9d entries/s(K)=% 11.3f total=%d\n",
map.getClass().getSimpleName(), size, size * loops / seconds / 1e3, total);
}
}
5.结果数据
图表数据如下。 例如,可以将其导入Excel并按Map类和大小进行排序,以手动制作图形。 您需要剪切并粘贴数据,然后使用文本到列。 可以使用折线图,为每个Map类手动选择一个系列的数据。 通过更改数据输出格式,也可以使用“ R”统计语言的ggplot。 忽略总数:这仅提供了有形输出,以避免优化循环。
JavaCodeGeeksIteratorArticle
Iterator performance test
HashMap size= 1 entries/s= 31290.754K total=-289049400605539520
TreeMap size= 1 entries/s= 50210.333K total=-331631386373881504
ConcurrentHashMap size= 1 entries/s= 15881.356K total=-91464608232057952
ConcurrentSkipListMap size= 1 entries/s= 42187.535K total=-254234286353018080
AirConcurrentMap size= 1 entries/s= 25577.125K total=-149805405784208032
---------------
HashMap size= 3 entries/s= 62664.626K total=-484691989675689270
TreeMap size= 3 entries/s= 66908.091K total=-550745245141063704
ConcurrentHashMap size= 3 entries/s= 38018.996K total=-211326922860746827
ConcurrentSkipListMap size= 3 entries/s= 71265.063K total=-488278692474832005
AirConcurrentMap size= 3 entries/s= 48540.146K total=-302163579336545082
---------------
HashMap size= 10 entries/s= 86701.181K total=-832795481348512598
TreeMap size= 10 entries/s= 87832.407K total=-916137658370092344
ConcurrentHashMap size= 10 entries/s= 73069.458K total=-502840045890573499
ConcurrentSkipListMap size= 10 entries/s= 96150.046K total=-880874881700377401
AirConcurrentMap size= 10 entries/s= 72001.056K total=-591224549191451578
---------------
HashMap size= 30 entries/s= 89419.363K total=-832238604657166224
TreeMap size= 30 entries/s= 92397.645K total=-915559545928720920
ConcurrentHashMap size= 30 entries/s= 71702.258K total=-502393457157098057
ConcurrentSkipListMap size= 30 entries/s= 103387.524K total=-880213560719307683
AirConcurrentMap size= 30 entries/s= 80807.271K total=-590716865563759348
---------------
HashMap size= 100 entries/s= 90540.307K total=-845663104709828079
TreeMap size= 100 entries/s= 96479.776K total=-930300717715488858
ConcurrentHashMap size= 100 entries/s= 69055.433K total=-512752383626539310
ConcurrentSkipListMap size= 100 entries/s= 111208.365K total=-897628029635465726
AirConcurrentMap size= 100 entries/s= 89071.481K total=-604453907970584431
---------------
HashMap size= 300 entries/s= 94846.852K total=-860347586557330269
TreeMap size= 300 entries/s= 94506.995K total=-944821983122037883
ConcurrentHashMap size= 300 entries/s= 65857.587K total=-522786710141245214
ConcurrentSkipListMap size= 300 entries/s= 112398.344K total=-915694233970137252
AirConcurrentMap size= 300 entries/s= 92168.052K total=-618862268508225735
---------------
HashMap size= 1000 entries/s= 74493.997K total=-852961390806337305
TreeMap size= 1000 entries/s= 80026.348K total=-937067262358689631
ConcurrentHashMap size= 1000 entries/s= 38450.309K total=-519070765727723030
ConcurrentSkipListMap size= 1000 entries/s= 112085.413K total=-904572392174355552
AirConcurrentMap size= 1000 entries/s= 89022.852K total=-609589031935380951
---------------
HashMap size= 3000 entries/s= 57470.417K total=-847037038798366713
TreeMap size= 3000 entries/s= 65963.172K total=-930168324830420495
ConcurrentHashMap size= 3000 entries/s= 41073.089K total=-514814334734654678
ConcurrentSkipListMap size= 3000 entries/s= 109217.866K total=-892841347702876464
AirConcurrentMap size= 3000 entries/s= 89175.845K total=-600361285087522951
---------------
HashMap size= 10000 entries/s= 46254.558K total=-846309210319384299
TreeMap size= 10000 entries/s= 49044.408K total=-929403633977808893
ConcurrentHashMap size= 10000 entries/s= 36385.473K total=-514246034592481772
ConcurrentSkipListMap size= 10000 entries/s= 99442.425K total=-891342788950136070
AirConcurrentMap size= 10000 entries/s= 85447.544K total=-599022098209904335
---------------
HashMap size= 30000 entries/s= 43723.556K total=-848942181585517584
TreeMap size= 30000 entries/s= 45253.915K total=-932143497084889069
ConcurrentHashMap size= 30000 entries/s= 32665.051K total=-516220137420939070
ConcurrentSkipListMap size= 30000 entries/s= 83393.494K total=-896231928805619954
AirConcurrentMap size= 30000 entries/s= 80619.262K total=-603845100973163554
---------------
HashMap size= 100000 entries/s= 40028.088K total=-849706795555554639
TreeMap size= 100000 entries/s= 41755.506K total=-932944794183923404
ConcurrentHashMap size= 100000 entries/s= 26064.027K total=-516724530444651670
ConcurrentSkipListMap size= 100000 entries/s= 46619.667K total=-897138307784594414
AirConcurrentMap size= 100000 entries/s= 75034.058K total=-605290263409285564
---------------
HashMap size= 300000 entries/s= 28271.140K total=-850157323063101369
TreeMap size= 300000 entries/s= 23442.635K total=-933312552546033574
ConcurrentHashMap size= 300000 entries/s= 22886.588K total=-517086645455936620
ConcurrentSkipListMap size= 300000 entries/s= 26852.530K total=-897567202447311134
AirConcurrentMap size= 300000 entries/s= 43406.800K total=-605991920028554584
---------------
HashMap size= 1000000 entries/s= 20762.874K total=-850266118577777400
TreeMap size= 1000000 entries/s= 21465.730K total=-933426629396373490
ConcurrentHashMap size= 1000000 entries/s= 17617.501K total=-517179596963620996
ConcurrentSkipListMap size= 1000000 entries/s= 17753.452K total=-897660153954995510
AirConcurrentMap size= 1000000 entries/s= 24726.115K total=-606121840885886155
---------------
HashMap size= 3000000 entries/s= 20859.307K total=-850290350569160265
TreeMap size= 3000000 entries/s= 17078.422K total=-933446707332090721
ConcurrentHashMap size= 3000000 entries/s= 19987.888K total=-517202444269781983
ConcurrentSkipListMap size= 3000000 entries/s= 23990.479K total=-897687847659433070
AirConcurrentMap size= 3000000 entries/s= 30472.006K total=-606157150359044044
---------------
HashMap size= 10000000 entries/s= 18594.336K total=-850335966429011695
TreeMap size= 10000000 entries/s= 14332.011K total=-933483200019971865
ConcurrentHashMap size= 10000000 entries/s= 17038.665K total=-517248060129633413
ConcurrentSkipListMap size= 10000000 entries/s= 18600.417K total=-897733463519284500
AirConcurrentMap size= 10000000 entries/s= 39037.289K total=-606248382078746904
---------------
ForEach() performance test
HashMap size= 1 entries/s= 60469.332K total=-429010055848020608
TreeMap size= 1 entries/s= 162720.446K total=-1192873323002853184
ConcurrentHashMap size= 1 entries/s= 39683.288K total=-238381128098095008
ConcurrentSkipListMap size= 1 entries/s= 125216.579K total=-742139402594604448
AirConcurrentMap size= 1 entries/s= 40199.780K total=-223453462147226592
---------------
HashMap size= 3 entries/s= 154792.076K total=-907351702836558858
TreeMap size= 3 entries/s= 209809.380K total=-1866688472587176884
ConcurrentHashMap size= 3 entries/s= 100788.166K total=-558021636792810008
ConcurrentSkipListMap size= 3 entries/s= 202179.445K total=-1380005440196857173
AirConcurrentMap size= 3 entries/s= 99654.211K total=-536118642462089017
---------------
HashMap size= 10 entries/s= 297297.392K total=-2080956150412338142
TreeMap size= 10 entries/s= 228262.234K total=-2782965758065995472
ConcurrentHashMap size= 10 entries/s= 189641.651K total=-1313404093180619596
ConcurrentSkipListMap size= 10 entries/s= 304427.266K total=-2602702896415550485
AirConcurrentMap size= 10 entries/s= 179131.091K total=-1257952993772621945
---------------
HashMap size= 30 entries/s= 305634.315K total=-2079066757218703314
TreeMap size= 30 entries/s= 224584.862K total=-2781566150975000263
ConcurrentHashMap size= 30 entries/s= 174917.912K total=-1312321443993669200
ConcurrentSkipListMap size= 30 entries/s= 354581.676K total=-2600502266614288915
AirConcurrentMap size= 30 entries/s= 254150.967K total=-1256373143380530839
---------------
HashMap size= 100 entries/s= 295763.376K total=-2123996537948400465
TreeMap size= 100 entries/s= 225375.420K total=-2816005249627104526
ConcurrentHashMap size= 100 entries/s= 168596.386K total=-1337959745143386554
ConcurrentSkipListMap size= 100 entries/s= 366342.358K total=-2656351051389612808
AirConcurrentMap size= 100 entries/s= 336305.468K total=-1307783105877232718
---------------
HashMap size= 300 entries/s= 335940.642K total=-2176036047793281607
TreeMap size= 300 entries/s= 213798.860K total=-2849343024468137449
ConcurrentHashMap size= 300 entries/s= 196158.746K total=-1368245793652746886
ConcurrentSkipListMap size= 300 entries/s= 348762.198K total=-2711376732587358984
AirConcurrentMap size= 300 entries/s= 435378.640K total=-1375390632080685627
---------------
HashMap size= 1000 entries/s= 312285.030K total=-2145888100702813327
TreeMap size= 1000 entries/s= 157007.240K total=-2834013084542617045
ConcurrentHashMap size= 1000 entries/s= 178919.552K total=-1350929801563960438
ConcurrentSkipListMap size= 1000 entries/s= 253518.140K total=-2687136797657993712
AirConcurrentMap size= 1000 entries/s= 449868.858K total=-1331959489090096491
---------------
HashMap size= 3000 entries/s= 269579.050K total=-2118053337323592431
TreeMap size= 3000 entries/s= 129271.886K total=-2820412724828116661
ConcurrentHashMap size= 3000 entries/s= 95870.060K total=-1340856888537750166
ConcurrentSkipListMap size= 3000 entries/s= 203849.473K total=-2666107139705649888
AirConcurrentMap size= 3000 entries/s= 443682.852K total=-1286068695711899563
---------------
HashMap size= 10000 entries/s= 100969.734K total=-2116523986910706773
TreeMap size= 10000 entries/s= 81240.085K total=-2819237854014952091
ConcurrentHashMap size= 10000 entries/s= 74229.656K total=-1339726640597926192
ConcurrentSkipListMap size= 10000 entries/s= 147733.052K total=-2664068913299591178
AirConcurrentMap size= 10000 entries/s= 399269.901K total=-1279844336850624703
---------------
HashMap size= 30000 entries/s= 77830.586K total=-2121068856388826590
TreeMap size= 30000 entries/s= 73801.711K total=-2823183557187296024
ConcurrentHashMap size= 30000 entries/s= 63503.394K total=-1343522194696030345
ConcurrentSkipListMap size= 30000 entries/s= 148907.153K total=-2671403338078408622
AirConcurrentMap size= 30000 entries/s= 450044.679K total=-1305454406448994036
---------------
HashMap size= 100000 entries/s= 57919.591K total=-2122150626578319295
TreeMap size= 100000 entries/s= 39468.243K total=-2823932886520250879
ConcurrentHashMap size= 100000 entries/s= 30273.873K total=-1344103393021081000
ConcurrentSkipListMap size= 100000 entries/s= 48766.187K total=-2672332261897079327
AirConcurrentMap size= 100000 entries/s= 307460.503K total=-1311189966514089586
---------------
HashMap size= 300000 entries/s= 42127.664K total=-2122825947560403955
TreeMap size= 300000 entries/s= 26508.090K total=-2824353316156729769
ConcurrentHashMap size= 300000 entries/s= 27206.845K total=-1344518179306734670
ConcurrentSkipListMap size= 300000 entries/s= 19172.093K total=-2672628537815403377
AirConcurrentMap size= 300000 entries/s= 152485.548K total=-1313414387297697136
---------------
HashMap size= 1000000 entries/s= 40607.148K total=-2123037200986959355
TreeMap size= 1000000 entries/s= 25159.911K total=-2824487462082592448
ConcurrentHashMap size= 1000000 entries/s= 30257.523K total=-1344677675643783997
ConcurrentSkipListMap size= 1000000 entries/s= 37742.151K total=-2672825003502099899
AirConcurrentMap size= 1000000 entries/s= 148954.277K total=-1314169618297632691
---------------
HashMap size= 3000000 entries/s= 43941.038K total=-2123087741997557902
TreeMap size= 3000000 entries/s= 18891.646K total=-2824508924703531557
ConcurrentHashMap size= 3000000 entries/s= 32007.894K total=-1344715062144774703
ConcurrentSkipListMap size= 3000000 entries/s= 21722.543K total=-2672849927836093703
AirConcurrentMap size= 3000000 entries/s= 126084.363K total=-1314312240875486125
---------------
HashMap size= 10000000 entries/s= 35724.715K total=-2123169850545290476
TreeMap size= 10000000 entries/s= 12693.940K total=-2824540855805427558
ConcurrentHashMap size= 10000000 entries/s= 27254.306K total=-1344783485934551848
ConcurrentSkipListMap size= 10000000 entries/s= 13572.712K total=-2672886420523974847
AirConcurrentMap size= 10000000 entries/s= 92726.047K total=-1314522073830802703
---------------
6.总结
使用某些预防措施的简单代码,可以获得可重复的,有意义的自定义Map性能测试。 此代码显示了其中一些问题。 该代码可轻松适应其他种类的Map测试和数据输出格式。
翻译自: https://www.javacodegeeks.com/2017/04/simple-map-iterator-performance-test.html
map iterator