Статьи

Серия тестов по простым решениям для кэширования в Java

Кэширование является очень распространенным решением, когда вы не хотите повторять интенсивные задачи процессора. Последние дни я тестировал параметры для выполнения кэширования с помощью 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