Кэширование является очень распространенным решением, когда вы не хотите повторять интенсивные задачи процессора. Последние дни я тестировал параметры для выполнения кэширования с помощью ConcurrentHashMap. В этом блоге я публикую первые результаты. Я использовал Heinz Kabutz ‘Performance Checker, чтобы сделать это. Я также добавил некоторые функции, основанные на моих чтениях из этой серии статей .
Я тестирую три разных реализации кеша: «Проверить ноль», «проверить карту» и «putIfAbsent» кэш. Код указан ниже результатов. Кроме того, я использую три разных размера кеша: 10 (маленький), 100000 (большой) и 1000000 (очень большой) возможные значения ключей. Опять же: кэш 10 единиц может иметь 10 различных значений ключа. Кэш 100000 единиц может иметь 100000 различных значений ключа и т. Д.
Ни один из вариантов реализации не показывает существенных различий в производительности, если предположить, что размер кэша эквивалентен. Это разочаровывает (!!), но также полезно знать. Это также немного удивительно, я думаю, потому что, например, кэш «putIfAbsent» кажется более сложным, чем другие. Кажется, что все решения уменьшают логарифмическую производительность, когда кэш увеличивается до очень больших размеров. Основной причиной этого должно быть увеличение времени на инициализацию кэша, потому что в моем тестовом жгуте я начинаю с пустых денег и фиксированного набора возможных значений ключа (т. Е. 10, 100000 или 1000000). Я подойду к серии тестов для полностью инициализированного кэша позже.
Зная, что я знаю сейчас, я бы порекомендовал использовать решение для кэширования putIfAbsent. Это связано с тем, что он имеет эквивалентную производительность, но дает вам большую гибкость при проектировании поведения вашего кэша в очень параллельных сценариях. Посмотрите
шаблонное решение моей последней статьи о многопоточности как пример более сложных вариантов использования.
Если вы заинтересованы в тестовом использовании, взгляните на реализации:
CacheSolution_CheckMap.java ,
CacheSolution_CheckNull.java и
CacheSolution_PutIfAbsent.java . Я очень ценю ваши комментарии!
Моя JVM была в смешанном режиме JIT и с опцией -server. Хорошо, давайте углубимся в это!
-XX:InitialHeapSize=49759808 -XX:MaxHeapSize=796156928 -XX:ParallelGCThreads=2 -XX:+PrintCommandLineFlags -XX:-UseLargePagesIndividualAllocation -XX:+UseParallelGC Check null - small cache - mixed mode Mean exec count Deviation JIT before JIT after CL before CL after 1981447,2 8210,4428 381 381 651 651 1956580,4 9408,1773 381 381 651 651 1991336,4 13575,1192 381 381 651 651 1947666,2 8564,2068 381 381 651 651 Check map - small cache - mixed mode Mean exec count Deviation JIT before JIT after CL before CL after 1794618,4 11201,6421 381 381 651 651 1790090,6 10716,0937 381 381 651 651 1824689,2 3967,281 381 381 651 651 1788582,6 3523,9609 381 381 651 651 putIfAbsent - small cache - mixed mode Mean exec count Deviation JIT before JIT after CL before CL after 1708336,4 6037,146 475 475 654 654 1706098,8 1804,7957 584 584 654 654 1738511,4 6360,8573 584 584 654 654 1711581 11009,8691 584 584 654 654 Check null - large cache - mixed mode Mean exec count Deviation JIT before JIT after CL before CL after 1243431,8 4671,9216 602 602 654 654 1231862,8 3457,7805 613 613 654 654 1243344,8 6136,7727 613 613 654 654 1220573,8 22964,319 613 613 654 654 Check map - large cache - mixed mode Mean exec count Deviation JIT before JIT after CL before CL after 1116376,6 2488,8944 625 625 654 654 1069693,2 73327,6869 636 636 654 654 1112667,4 7746,2007 636 636 654 654 1102652,4 1648,8972 636 636 654 654 putIfAbsent - large cache - mixed mode Mean exec count Deviation JIT before JIT after CL before CL after 966868 2609,4972 642 642 654 654 968215,4 1688,7719 653 653 654 654 982656,8 6600,9725 659 659 654 654 973622,2 3884,5465 659 659 654 654 Check null - very large cache - mixed mode Mean exec count Deviation JIT before JIT after CL before CL after 757436 54966,9938 659 659 654 654 778233,2 24416,1994 659 659 654 654 783227 23614,2381 659 659 654 654 777516,2 22406,1194 659 659 655 655 Check map - very large cache - mixed mode Mean exec count Deviation JIT before JIT after CL before CL after 741914,2 22100,5588 659 659 655 655 738258 22120,656 659 659 655 655 741697 22608,6525 659 659 655 655 738903,8 21126,0641 665 665 656 656 putIfAbsent - very large cache - mixed mode Mean exec count Deviation JIT before JIT after CL before CL after 703047,4 14771,0501 665 665 656 656 698942,8 15885,6482 665 665 656 656 702126,6 16192,2101 665 665 656 656 699374,8 16967,6066 665 665 656 656
5 тестовых прогонов каждые / 500 мс каждый тестовый прогон
CL до / после = классы Загружается до и после тестового набора
JIT до / после = общее время JIT до и после тестового набора
Маленький кэш = 10 единиц
Большой кэш 100000 единиц
Очень большой кэш = 1000000 единиц
Проверьте нулевой кеш
private Map<String, String> commandCache = new ConcurrentHashMap<String, String>(); @Override public Object[] execute(Object... arguments) { String clientCommand = (String) arguments[0]; String serverCommand = commandCache.get(clientCommand); if (serverCommand == null) { lock.lock(); try { if (commandCache.containsKey(clientCommand)) serverCommand = commandCache.get(clientCommand); else { // do something CPU intensive (is not relevant for test result here) serverCommand = "dummy string"; commandCache.put(clientCommand, serverCommand); } } finally { lock.unlock(); } } Object[] result = new Object[] { serverCommand }; return result; }
Check map cache
private Map<String, String> commandCache = new ConcurrentHashMap<String, String>(); @Override public Object[] execute(Object... arguments) { String clientCommand = (String) arguments[0]; String serverCommand = null; if (commandCache.containsKey(clientCommand)) { serverCommand = commandCache.get(clientCommand); } else { lock.lock(); try { if (commandCache.containsKey(clientCommand)) { serverCommand = commandCache.get(clientCommand); } else { // do something CPU intensive (is not relevant for test result) serverCommand = "dummy string"; commandCache.put(clientCommand, serverCommand); } } finally { lock.unlock(); } } Object[] result = new Object[] { serverCommand }; return result; }
The putIfAbsent cache
private ConcurrentHashMap<String, SomeCPUIntenseTask> commandCache = new ConcurrentHashMap<String, SomeCPUIntenseTask>(); @Override public Object[] execute(Object... arguments) { String clientCommand = (String) arguments[0]; SomeCPUIntenseTask newServerCommand; SomeCPUIntenseTask serverCommand = commandCache.putIfAbsent(clientCommand, newServerCommand = new SomeCPUIntenseTask()); if (serverCommand == null) { serverCommand = newServerCommand; } return new Object[]{serverCommand.doCPUIntenseTask()}; } public class SomeCPUIntenseTask { private Object taskResult = null; public Object doCPUIntenseTask() { if (taskResult != null) return taskResult; // avoid locking overhead else { try { lock.lock(); if (taskResult != null) // two threads may have been in race condition return taskResult; // perform CPU intense task taskResult = new Object(); return taskResult; } finally { lock.unlock(); } } } }
From http://niklasschlimm.blogspot.com/2011/09/benchmark-series-on-simple-caching.html