From 5d65e648cd49b4d26ffba8919286dffdb954f236 Mon Sep 17 00:00:00 2001 From: liningrui Date: Wed, 26 Aug 2020 00:11:18 +0800 Subject: [PATCH 1/2] Implement a ThreadLocalCache to improve read perf Change-Id: I372fb5a56f374fc3993d8bdcbbb0ef07b5f160c7 --- .../hugegraph/backend/cache/CacheManager.java | 11 ++ .../cache/CachedSchemaTransaction.java | 2 +- .../backend/cache/ThreadLocalCache.java | 103 ++++++++++++++++++ 3 files changed, 115 insertions(+), 1 deletion(-) create mode 100644 hugegraph-core/src/main/java/com/baidu/hugegraph/backend/cache/ThreadLocalCache.java diff --git a/hugegraph-core/src/main/java/com/baidu/hugegraph/backend/cache/CacheManager.java b/hugegraph-core/src/main/java/com/baidu/hugegraph/backend/cache/CacheManager.java index 319cf75685..b4a190be8b 100644 --- a/hugegraph-core/src/main/java/com/baidu/hugegraph/backend/cache/CacheManager.java +++ b/hugegraph-core/src/main/java/com/baidu/hugegraph/backend/cache/CacheManager.java @@ -112,6 +112,17 @@ public Cache cache(String name, long capacity) { return cache; } + public Cache threadLocalCache(String name, long capacity) { + if (!this.caches.containsKey(name)) { + this.caches.putIfAbsent(name, new ThreadLocalCache<>(capacity)); + } + @SuppressWarnings("unchecked") + Cache cache = (Cache) this.caches.get(name); + E.checkArgument(cache instanceof ThreadLocalCache, + "Invalid cache implement: %s", cache.getClass()); + return cache; + } + public Cache offheapCache(HugeGraph graph, String name, long capacity, long avgElemSize) { if (!this.caches.containsKey(name)) { diff --git a/hugegraph-core/src/main/java/com/baidu/hugegraph/backend/cache/CachedSchemaTransaction.java b/hugegraph-core/src/main/java/com/baidu/hugegraph/backend/cache/CachedSchemaTransaction.java index a8d2ed9575..632b2853c4 100644 --- a/hugegraph-core/src/main/java/com/baidu/hugegraph/backend/cache/CachedSchemaTransaction.java +++ b/hugegraph-core/src/main/java/com/baidu/hugegraph/backend/cache/CachedSchemaTransaction.java @@ -75,7 +75,7 @@ private Cache cache(String prefix) { final String name = prefix + "-" + this.graphName(); final long capacity = conf.get(CoreOptions.SCHEMA_CACHE_CAPACITY); // NOTE: must disable schema cache-expire due to getAllSchema() - return CacheManager.instance().cache(name, capacity); + return CacheManager.instance().threadLocalCache(name, capacity); } private CachedTypes cachedTypes() { diff --git a/hugegraph-core/src/main/java/com/baidu/hugegraph/backend/cache/ThreadLocalCache.java b/hugegraph-core/src/main/java/com/baidu/hugegraph/backend/cache/ThreadLocalCache.java new file mode 100644 index 0000000000..6666207dba --- /dev/null +++ b/hugegraph-core/src/main/java/com/baidu/hugegraph/backend/cache/ThreadLocalCache.java @@ -0,0 +1,103 @@ +/* + * Copyright 2017 HugeGraph Authors + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with this + * work for additional information regarding copyright ownership. The ASF + * licenses this file to You under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ + +package com.baidu.hugegraph.backend.cache; + +import java.util.HashMap; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.concurrent.CopyOnWriteArrayList; +import java.util.function.Consumer; + +import com.baidu.hugegraph.iterator.MapperIterator; +import com.baidu.hugegraph.util.E; + +public class ThreadLocalCache extends AbstractCache { + + private final List> allCaches; + private final ThreadLocal> localCache; + + public ThreadLocalCache(long capacity) { + super(capacity); + this.allCaches = new CopyOnWriteArrayList<>(); + this.localCache = new ThreadLocal<>(); + } + + @Override + public boolean containsKey(K id) { + return this.getOrNewCache().containsKey(id); + } + + @Override + public void traverse(Consumer consumer) { + E.checkNotNull(consumer, "consumer"); + this.getOrNewCache().values().forEach(consumer); + } + + @Override + public void clear() { + this.allCaches.forEach(HashMap::clear); + } + + @Override + protected V access(K id) { + return this.getOrNewCache().get(id); + } + + @Override + protected boolean write(K id, V value) { + this.getOrNewCache(); + this.allCaches.forEach(cache -> cache.put(id, value)); + return true; + } + + @Override + protected void remove(K id) { + this.allCaches.forEach(cache -> cache.remove(id)); + } + + @Override + protected Iterator> nodes() { + Iterator> iter = this.getOrNewCache().entrySet().iterator(); + return new MapperIterator<>(iter, kvEntry -> { + return new CacheNode<>(kvEntry.getKey(), kvEntry.getValue()); + }); + } + + @Override + public long size() { + return this.getOrNewCache().size(); + } + + private Cache getOrNewCache() { + Cache cache = this.localCache.get(); + if (cache == null) { + cache = new Cache<>(); + this.localCache.set(cache); + this.allCaches.add(cache); + } + return cache; + } + + private static class Cache extends HashMap { + + private static final long serialVersionUID = -3426955734239657957L; + } +} From 88aaaba3a4afe06c1660a505a0cd610cbe892e3f Mon Sep 17 00:00:00 2001 From: liningrui Date: Wed, 26 Aug 2020 10:16:00 +0800 Subject: [PATCH 2/2] add optimistic cache Change-Id: Id3a39da5cc683fc90f07ad5b8eaae9db5eb9244d --- .../hugegraph/backend/cache/CacheManager.java | 11 ++ .../cache/CachedSchemaTransaction.java | 2 +- .../backend/cache/OptimisticCache.java | 149 ++++++++++++++++++ 3 files changed, 161 insertions(+), 1 deletion(-) create mode 100644 hugegraph-core/src/main/java/com/baidu/hugegraph/backend/cache/OptimisticCache.java diff --git a/hugegraph-core/src/main/java/com/baidu/hugegraph/backend/cache/CacheManager.java b/hugegraph-core/src/main/java/com/baidu/hugegraph/backend/cache/CacheManager.java index b4a190be8b..8ec9764f66 100644 --- a/hugegraph-core/src/main/java/com/baidu/hugegraph/backend/cache/CacheManager.java +++ b/hugegraph-core/src/main/java/com/baidu/hugegraph/backend/cache/CacheManager.java @@ -123,6 +123,17 @@ public Cache threadLocalCache(String name, long capacity) { return cache; } + public Cache optimisticCache(String name, long capacity) { + if (!this.caches.containsKey(name)) { + this.caches.putIfAbsent(name, new OptimisticCache<>(capacity)); + } + @SuppressWarnings("unchecked") + Cache cache = (Cache) this.caches.get(name); + E.checkArgument(cache instanceof OptimisticCache, + "Invalid cache implement: %s", cache.getClass()); + return cache; + } + public Cache offheapCache(HugeGraph graph, String name, long capacity, long avgElemSize) { if (!this.caches.containsKey(name)) { diff --git a/hugegraph-core/src/main/java/com/baidu/hugegraph/backend/cache/CachedSchemaTransaction.java b/hugegraph-core/src/main/java/com/baidu/hugegraph/backend/cache/CachedSchemaTransaction.java index 632b2853c4..a8d2ed9575 100644 --- a/hugegraph-core/src/main/java/com/baidu/hugegraph/backend/cache/CachedSchemaTransaction.java +++ b/hugegraph-core/src/main/java/com/baidu/hugegraph/backend/cache/CachedSchemaTransaction.java @@ -75,7 +75,7 @@ private Cache cache(String prefix) { final String name = prefix + "-" + this.graphName(); final long capacity = conf.get(CoreOptions.SCHEMA_CACHE_CAPACITY); // NOTE: must disable schema cache-expire due to getAllSchema() - return CacheManager.instance().threadLocalCache(name, capacity); + return CacheManager.instance().cache(name, capacity); } private CachedTypes cachedTypes() { diff --git a/hugegraph-core/src/main/java/com/baidu/hugegraph/backend/cache/OptimisticCache.java b/hugegraph-core/src/main/java/com/baidu/hugegraph/backend/cache/OptimisticCache.java new file mode 100644 index 0000000000..8eede10ff2 --- /dev/null +++ b/hugegraph-core/src/main/java/com/baidu/hugegraph/backend/cache/OptimisticCache.java @@ -0,0 +1,149 @@ +/* + * Copyright 2017 HugeGraph Authors + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with this + * work for additional information regarding copyright ownership. The ASF + * licenses this file to You under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ + +package com.baidu.hugegraph.backend.cache; + +import java.util.HashMap; +import java.util.Iterator; +import java.util.Map; +import java.util.concurrent.locks.StampedLock; +import java.util.function.Consumer; + +import com.baidu.hugegraph.iterator.MapperIterator; +import com.baidu.hugegraph.util.E; + +public class OptimisticCache extends AbstractCache { + + private final StampedLock stampedLock; + private final Map cache; + + public OptimisticCache(long capacity) { + super(capacity); + this.stampedLock = new StampedLock(); + this.cache = new HashMap<>(); + } + + @Override + protected V access(K id) { + final StampedLock stampedLock = this.stampedLock; + long stamp = stampedLock.tryOptimisticRead(); + V value = this.cache.get(id); + if (!stampedLock.validate(stamp)) { + stamp = stampedLock.readLock(); + try { + value = this.cache.get(id); + } finally { + stampedLock.unlockRead(stamp); + } + } + return value; + } + + @Override + protected boolean write(K id, V value) { + final StampedLock stampedLock = this.stampedLock; + final long stamp = stampedLock.writeLock(); + try { + this.cache.put(id, value); + } finally { + stampedLock.unlockWrite(stamp); + } + return true; + } + + @Override + protected void remove(K id) { + final StampedLock stampedLock = this.stampedLock; + final long stamp = stampedLock.writeLock(); + try { + this.cache.remove(id); + } finally { + stampedLock.unlockWrite(stamp); + } + } + + @Override + protected Iterator> nodes() { + final StampedLock stampedLock = this.stampedLock; + final long stamp = stampedLock.readLock(); + try { + Iterator> iter = this.cache.entrySet().iterator(); + return new MapperIterator<>(iter, kvEntry -> { + return new CacheNode<>(kvEntry.getKey(), kvEntry.getValue()); + }); + } finally { + stampedLock.unlockRead(stamp); + } + } + + @Override + public boolean containsKey(K id) { + final StampedLock stampedLock = this.stampedLock; + long stamp = stampedLock.tryOptimisticRead(); + boolean existed = this.cache.containsKey(id); + if (!stampedLock.validate(stamp)) { + stamp = stampedLock.readLock(); + try { + existed = this.cache.containsKey(id); + } finally { + stampedLock.unlockRead(stamp); + } + } + return existed; + } + + @Override + public void traverse(Consumer consumer) { + E.checkNotNull(consumer, "consumer"); + final StampedLock stampedLock = this.stampedLock; + final long stamp = stampedLock.readLock(); + try { + this.cache.values().forEach(consumer); + } finally { + stampedLock.unlockRead(stamp); + } + } + + @Override + public void clear() { + final StampedLock stampedLock = this.stampedLock; + final long stamp = stampedLock.writeLock(); + try { + this.cache.clear(); + } finally { + stampedLock.unlockWrite(stamp); + } + } + + @Override + public long size() { + final StampedLock stampedLock = this.stampedLock; + long stamp = stampedLock.tryOptimisticRead(); + int size = this.cache.size(); + if (!stampedLock.validate(stamp)) { + stamp = stampedLock.readLock(); + try { + size = this.cache.size(); + } finally { + stampedLock.unlockRead(stamp); + } + } + return size; + } +}