Я начал тестировать способы поиска ключа по значению в кеше Guava и заметил странное поведение, связанное с уровнем параллелизма. Я не уверен, является ли это ошибкой или неопределенным поведением или, может быть, даже ожидаемым, но не указанным.
Мой тест должен находить ключ по значению в кеше Guava, что, как я знаю, не обычное дело.
Это мой полный эталонный класс:
@Fork(4)
@State(Scope.Benchmark)
@BenchmarkMode(Mode.AverageTime)
@OutputTimeUnit(TimeUnit.MICROSECONDS)
@Warmup(iterations = 1, time = 100, timeUnit = TimeUnit.MILLISECONDS)
@Measurement(iterations = 4, time = 100, timeUnit = TimeUnit.MILLISECONDS)
public class ValueByKey {
private Long counter = 0L;
private final int MAX = 2500;
private final LoadingCache<String, Long> stringToLong = CacheBuilder.newBuilder()
.concurrencyLevel(1)
.maximumSize(MAX + 5)
.build(new CacheLoader<String, Long>() {
public Long load(String mString) {
return generateIdByString(mString);
}
});
private final Map<String, Long> mHashMap = new Hashtable<>(MAX);
private final Map<String, Long> concurrentHashMap = new ConcurrentHashMap<>(MAX);
@Setup(Level.Trial)
public void setup() {
// Populate guava cache
for(int i = 0; i <= MAX; i++) {
try {
stringToLong.get(UUID.randomUUID().toString());
} catch (ExecutionException e) {
e.printStackTrace();
System.exit(1);
}
}
}
@Benchmark
public String stringToIdByIteration() {
Long randomNum = ThreadLocalRandom.current().nextLong(1L, MAX);
for(Map.Entry<String, Long> entry : stringToLong.asMap().entrySet()) {
if(Objects.equals(randomNum, entry.getValue())) {
return entry.getKey();
}
}
System.out.println("Returning null as value not found " + randomNum);
return null;
}
@Benchmark
public String stringToIdByIterationHashTable() {
Long randomNum = ThreadLocalRandom.current().nextLong(1L, MAX);
for(Map.Entry<String, Long> entry : mHashMap.entrySet()) {
if(Objects.equals(randomNum, entry.getValue())) {
return entry.getKey();
}
}
System.out.println("Returning null as value not found " + randomNum);
return null;
}
@Benchmark
public String stringToIdByIterationConcurrentHashMap() {
Long randomNum = ThreadLocalRandom.current().nextLong(1L, MAX);
for(Map.Entry<String, Long> entry : concurrentHashMap.entrySet()) {
if(Objects.equals(randomNum, entry.getValue())) {
return entry.getKey();
}
}
System.out.println("concurrentHashMap Returning null as value not found " + randomNum);
return null;
}
private Long generateIdByString(final String mString) {
mHashMap.put(mString, counter++);
concurrentHashMap.put(mString, counter);
return counter;
}
}
Я заметил, что когда я меняю .concurrencyLevel(1)
на число, отличное от 1, я начинаю терять данные. Следующий вывод относится к уровню параллелизма 4:
Iteration 1: Returning null as value not found 107
Returning null as value not found 43
Returning null as value not found 20
Returning null as value not found 77
Returning null as value not found 127
Returning null as value not found 35
Returning null as value not found 83
Returning null as value not found 43
Returning null as value not found 127
Returning null as value not found 107
Returning null as value not found 83
Returning null as value not found 82
Returning null as value not found 40
Returning null as value not found 58
Returning null as value not found 127
Returning null as value not found 114
Returning null as value not found 119
Returning null as value not found 43
Returning null as value not found 114
Returning null as value not found 18
Returning null as value not found 58
66.778 us/op
Я заметил, что никогда не теряю данные при использовании HashMap
или HashTable
для использования одного и того же кода, он также работает намного лучше:
Benchmark Mode Cnt Score Error Units
ValueByKey.stringToIdByIteration avgt 16 58.637 ± 15.094 us/op
ValueByKey.stringToIdByIterationConcurrentHashMap avgt 16 16.148 ± 2.046 us/op
ValueByKey.stringToIdByIterationHashTable avgt 16 11.705 ± 1.095 us/op
Является ли мой код неправильным или Guava не может правильно обрабатывать секционированные HashTable с уровнем параллелизма выше 1?
- Параметр уровня параллелизма используется для внутреннего разделения таблицы таким образом, чтобы обновления могли выполняться без конфликтов.
- Идеальным параметром было бы максимальное количество потоков, которые потенциально могут одновременно обращаться к кешу.