Skip to content
Closed
Show file tree
Hide file tree
Changes from 48 commits
Commits
Show all changes
94 commits
Select commit Hold shift + click to select a range
d4e3901
use caffeine instead of guava cache
LuciferYang Feb 1, 2021
a3d794a
fix compile error
LuciferYang Feb 1, 2021
85dc7b9
remove some change
LuciferYang Feb 1, 2021
399f5b2
fix exception type
LuciferYang Feb 1, 2021
dbb46dc
use cache writer to sync mode
LuciferYang Feb 1, 2021
01f99fe
remove listener
LuciferYang Feb 1, 2021
18813e9
add comments
LuciferYang Feb 1, 2021
3f13689
fix test suites
LuciferYang Feb 2, 2021
39032e6
Merge branch 'upmaster' into guava-cache-to-caffeine
LuciferYang Feb 8, 2021
674af31
Merge branch 'guava-cache-to-caffeine' of github.com:LuciferYang/spar…
LuciferYang Feb 8, 2021
5f08d0f
update deps
LuciferYang Feb 8, 2021
0c5382a
fix unused import
LuciferYang Feb 8, 2021
4761a5b
fix import
LuciferYang Feb 8, 2021
4b49b84
fix redundant space
LuciferYang Feb 8, 2021
1805f34
Merge branch 'upmaster' into guava-cache-to-caffeine
LuciferYang Mar 22, 2021
adc6d92
revert change of spark-deps
LuciferYang Mar 22, 2021
bdb522b
Merge branch 'upmaster' into guava-cache-to-caffeine
LuciferYang Mar 23, 2021
ac35393
update deps doc
LuciferYang Mar 23, 2021
b116f87
upgrade caffeine to 3.0.1
LuciferYang Mar 23, 2021
bd46b67
use evictionListener instead of CacheWriter
LuciferYang Mar 23, 2021
8842fa3
downgrade to 2.9.0
LuciferYang Mar 23, 2021
8985d6d
rewrite invalidate method in ApplicationCache
LuciferYang Mar 24, 2021
b568fcc
revert ExecutorSuite
LuciferYang Mar 24, 2021
ca9d58d
use custom executor
LuciferYang Mar 24, 2021
97c3c74
fix ExecutorSuite
LuciferYang Mar 24, 2021
f47cdb8
fix format
LuciferYang Mar 24, 2021
425c345
ExternalShuffleBlockResolver change to use CaffeinatedGuava
LuciferYang Mar 24, 2021
e0006c6
RemoteBlockPushResolver change to use CaffeinatedGuava
LuciferYang Mar 24, 2021
c02bc5b
RemoteBlockPushResolver change to use CaffeinatedGuava
LuciferYang Mar 24, 2021
c1c3cef
ApplicationCache change to use CaffeinatedGuava
LuciferYang Mar 24, 2021
c9f45ae
ApplicationCache change to use CaffeinatedGuava
LuciferYang Mar 24, 2021
24c361b
ReliableCheckpointRDD change to use CaffeinatedGuava
LuciferYang Mar 24, 2021
2b3072e
BlockManagerId change to use CaffeinatedGuava
LuciferYang Mar 24, 2021
636bfb5
Utils change to use CaffeinatedGuava
LuciferYang Mar 24, 2021
825b69a
Utils change to use CaffeinatedGuava
LuciferYang Mar 24, 2021
d1315c3
fix compile
LuciferYang Mar 24, 2021
32d9ce0
fix compile
LuciferYang Mar 24, 2021
8d6ad81
add caffeine guava to sql/core pom.xml
LuciferYang Mar 24, 2021
cf30234
remvoe type def of blockManagerIdCache
LuciferYang Mar 25, 2021
5eb75fa
remvoe type def of cachedPreferredLocations
LuciferYang Mar 25, 2021
7129802
try revert to use removalListener
LuciferYang Mar 25, 2021
045d3dc
Add comments
LuciferYang Mar 25, 2021
554b5a5
revert BlockManagerId
LuciferYang Mar 25, 2021
f488a3b
revert change of BlockManagerId
LuciferYang Mar 25, 2021
89abb61
relocation exclude guava cache
LuciferYang Mar 25, 2021
1a24ed4
Add LocalCacheBenchmark to compare guava cache and Caffeine
LuciferYang Mar 29, 2021
96e6cc8
add comments
LuciferYang Mar 29, 2021
7028ffa
Add benchmark result
LuciferYang Mar 29, 2021
3fad6ef
Compatible with Scala 2.13
LuciferYang Mar 30, 2021
aa742d3
fix import order
LuciferYang Mar 30, 2021
95d5c2e
update benchmark result
LuciferYang Mar 30, 2021
3ba2574
use guava cache CacheLoader in SubExprEvaluationRuntime
LuciferYang Mar 30, 2021
8995d72
remove site.ycsb
LuciferYang Mar 30, 2021
f61b041
Merge branch 'upmaster' into guava-cache-to-caffeine
LuciferYang Apr 8, 2021
6146529
Merge branch 'upmaster' into guava-cache-to-caffeine
LuciferYang Apr 28, 2021
7c53f73
Merge branch 'upmaster' into guava-cache-to-caffeine
LuciferYang Jun 28, 2021
a069bff
update deps file
LuciferYang Jun 28, 2021
1cbebd3
remove dep of caffeine:guava 1
LuciferYang Jun 28, 2021
52037e1
revert some changes
LuciferYang Jun 28, 2021
8391e8b
remove dep of caffeine:guava 2
LuciferYang Jun 28, 2021
0e3c5fb
remove caffeine:guvava from core module
LuciferYang Jun 29, 2021
4cb428d
format
LuciferYang Jun 29, 2021
35bea2d
remove caffeine:guava from sql core module
LuciferYang Jun 29, 2021
f314403
remove caffeine:guava from sql catalyst module
LuciferYang Jun 29, 2021
994fcee
remove caffeine:guava from parent pom
LuciferYang Jun 29, 2021
a64b280
new guava to caffeine
LuciferYang Jun 29, 2021
a93e37e
remove empty line
LuciferYang Jun 29, 2021
56d41f1
Merge branch 'upmaster' into guava-cache-to-caffeine
LuciferYang Jun 29, 2021
d7610ac
update deps file
LuciferYang Jun 29, 2021
93784cc
fix test in catalyst module
LuciferYang Jun 29, 2021
db1de65
remove unused import
LuciferYang Jun 29, 2021
47b0bf8
fix test
LuciferYang Jul 1, 2021
34d31fd
Merge branch 'upmaster' into guava-cache-to-caffeine
LuciferYang Jul 1, 2021
93330ce
upgrade caffeine to 2.9.1
LuciferYang Jul 1, 2021
e3b81c5
revert test change
LuciferYang Jul 5, 2021
74e2ac3
Merge branch 'upmaster' into guava-cache-to-caffeine
LuciferYang Jul 5, 2021
7506999
Merge branch 'upmaster' into guava-cache-to-caffeine
LuciferYang Jul 8, 2021
68193a9
add scaladoc
LuciferYang Jul 9, 2021
ee05314
Merge branch 'upmaster' into guava-cache-to-caffeine
LuciferYang Jul 12, 2021
db9cc56
Merge branch 'upmaster' into guava-cache-to-caffeine
LuciferYang Jul 16, 2021
5423f36
Merge branch 'upmaster' into guava-cache-to-caffeine
LuciferYang Jul 20, 2021
c6b9dc6
Merge branch 'upmaster' into guava-cache-to-caffeine
LuciferYang Jul 21, 2021
0743e3b
Merge branch 'upmaster' into guava-cache-to-caffeine
LuciferYang Jul 22, 2021
c5c84cd
Merge branch 'upmaster' into guava-cache-to-caffeine
LuciferYang Jul 26, 2021
f2a656a
update deps file
LuciferYang Jul 26, 2021
d69df8e
fix sunchao's comments
LuciferYang Jul 27, 2021
9cd9c35
remove unsued imports
LuciferYang Jul 27, 2021
7b360a7
revert for compile
LuciferYang Jul 27, 2021
33f5353
Merge branch 'upmaster' into guava-cache-to-caffeine
LuciferYang Jul 27, 2021
87def1a
Merge branch 'upmaster' into guava-cache-to-caffeine
LuciferYang Jul 27, 2021
6c74fc6
Merge branch 'upmaster' into guava-cache-to-caffeine
LuciferYang Jul 29, 2021
5a75b2c
Merge branch 'upmaster' into guava-cache-to-caffeine
LuciferYang Jul 29, 2021
706a68d
Merge branch 'upmaster' into guava-cache-to-caffeine
LuciferYang Aug 2, 2021
81f863f
Merge branch 'upmaster' into guava-cache-to-caffeine
LuciferYang Aug 4, 2021
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions common/network-shuffle/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,14 @@
<artifactId>slf4j-api</artifactId>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>com.github.ben-manes.caffeine</groupId>
<artifactId>caffeine</artifactId>
</dependency>
<dependency>
<groupId>com.github.ben-manes.caffeine</groupId>
<artifactId>guava</artifactId>
</dependency>

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could we avoid this since it depends on Guava and guava conflicts can be a pain to resolve.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is a good idea, but there are some interface differences between Caffeine and Guava Cache, and spark just uses these interfaces. For example, SessionCatalog uses V get(K key, Callable<? extends V> valueLoader) method only define in Guava Cache API

@ben-manes ben-manes Jun 28, 2021

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could you use Caffeine's V get(K key, Function<? extends K, ? extends V> mappingFunction) instead? That should be equivalent but be friendly for method references.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, I'm trying to get rid of the dependence on com.github.ben-manes.caffeine:guava in this way

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@holdenk @ben-manes All the dependencies related to com.github.ben-manes.caffeine: guava have been removed.

If all test passed , I will try to upgrade caffeine from 2.9.0 to 2.9.1 in this pr

<dependency>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,11 +32,11 @@
import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.github.benmanes.caffeine.cache.Caffeine;
import com.github.benmanes.caffeine.guava.CaffeinatedGuava;
import com.google.common.annotations.VisibleForTesting;
import com.google.common.cache.CacheBuilder;
import com.google.common.cache.CacheLoader;
import com.google.common.cache.LoadingCache;
import com.google.common.cache.Weigher;
import com.google.common.collect.Maps;
import org.iq80.leveldb.DB;
import org.iq80.leveldb.DBIterator;
Expand Down Expand Up @@ -111,19 +111,15 @@ public ExternalShuffleBlockResolver(TransportConf conf, File registeredExecutorF
this.registeredExecutorFile = registeredExecutorFile;
String indexCacheSize = conf.get("spark.shuffle.service.index.cache.size", "100m");
CacheLoader<File, ShuffleIndexInformation> indexCacheLoader =
new CacheLoader<File, ShuffleIndexInformation>() {
public ShuffleIndexInformation load(File file) throws IOException {
return new ShuffleIndexInformation(file);
}
};
shuffleIndexCache = CacheBuilder.newBuilder()
.maximumWeight(JavaUtils.byteStringAsBytes(indexCacheSize))
.weigher(new Weigher<File, ShuffleIndexInformation>() {
public int weigh(File file, ShuffleIndexInformation indexInfo) {
return indexInfo.getSize();
new CacheLoader<File, ShuffleIndexInformation>() {
public ShuffleIndexInformation load(File file) throws IOException {
return new ShuffleIndexInformation(file);
}
})
.build(indexCacheLoader);
};
Caffeine<File, ShuffleIndexInformation> builder = Caffeine.newBuilder()
.maximumWeight(JavaUtils.byteStringAsBytes(indexCacheSize))
.weigher((file, indexInfo) -> indexInfo.getSize());
shuffleIndexCache = CaffeinatedGuava.build(builder, indexCacheLoader);
db = LevelDBProvider.initLevelDB(this.registeredExecutorFile, CURRENT_VERSION, mapper);
if (db != null) {
executors = reloadRegisteredExecutors(db);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,13 +38,13 @@
import java.util.concurrent.Executor;
import java.util.concurrent.Executors;

import com.github.benmanes.caffeine.cache.Caffeine;
import com.github.benmanes.caffeine.guava.CaffeinatedGuava;
import com.google.common.annotations.VisibleForTesting;
import com.google.common.base.Objects;
import com.google.common.base.Preconditions;
import com.google.common.cache.CacheBuilder;
import com.google.common.cache.CacheLoader;
import com.google.common.cache.LoadingCache;
import com.google.common.cache.Weigher;
import com.google.common.collect.Maps;
import com.google.common.primitives.Ints;
import com.google.common.primitives.Longs;
Expand Down Expand Up @@ -103,10 +103,10 @@ public ShuffleIndexInformation load(File file) throws IOException {
return new ShuffleIndexInformation(file);
}
};
indexCache = CacheBuilder.newBuilder()
Caffeine<File, ShuffleIndexInformation> builder = Caffeine.newBuilder()
.maximumWeight(conf.mergedIndexCacheSize())
.weigher((Weigher<File, ShuffleIndexInformation>) (file, indexInfo) -> indexInfo.getSize())
.build(indexCacheLoader);
.weigher((file, indexInfo) -> indexInfo.getSize());
indexCache = CaffeinatedGuava.build(builder, indexCacheLoader);
this.errorHandler = new ErrorHandler.BlockPushErrorHandler();
}

Expand Down
14 changes: 14 additions & 0 deletions core/benchmarks/LocalCacheBenchmark-jdk11-results.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
================================================================================================
Loading Cache
================================================================================================

OpenJDK 64-Bit Server VM 11.0.8+10-LTS on Mac OS X 10.15.7
Intel(R) Core(TM) i5-7360U CPU @ 2.30GHz
Loading Cache: Best Time(ms) Avg Time(ms) Stdev(ms) Rate(M/s) Per Row(ns) Relative
--------------------------------------------------------------------------------------------------------------------------
Guava Cache 5 6 0 15.2 65.6 1.0X
Caffeine 2 2 1 46.9 21.3 3.1X
CaffeinatedGuava with Guava CacheLoader 2 2 0 46.0 21.8 3.0X
CaffeinatedGuava with Caffeine CacheLoader 2 2 0 46.1 21.7 3.0X


14 changes: 14 additions & 0 deletions core/benchmarks/LocalCacheBenchmark-results.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
================================================================================================
Loading Cache
================================================================================================

OpenJDK 64-Bit Server VM 1.8.0_232-b18 on Mac OS X 10.15.7
Intel(R) Core(TM) i5-7360U CPU @ 2.30GHz
Loading Cache: Best Time(ms) Avg Time(ms) Stdev(ms) Rate(M/s) Per Row(ns) Relative
--------------------------------------------------------------------------------------------------------------------------
Guava Cache 5 6 2 15.2 65.7 1.0X
Caffeine 2 2 1 41.2 24.3 2.7X
CaffeinatedGuava with Guava CacheLoader 2 2 0 44.0 22.7 2.9X
CaffeinatedGuava with Caffeine CacheLoader 2 2 0 44.2 22.6 2.9X


8 changes: 8 additions & 0 deletions core/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,14 @@
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
</dependency>
<dependency>
<groupId>com.github.ben-manes.caffeine</groupId>
<artifactId>caffeine</artifactId>
</dependency>
<dependency>
<groupId>com.github.ben-manes.caffeine</groupId>
<artifactId>guava</artifactId>
</dependency>
<dependency>
<groupId>com.twitter</groupId>
<artifactId>chill_${scala.binary.version}</artifactId>
Expand Down
4 changes: 2 additions & 2 deletions core/src/main/scala/org/apache/spark/SparkEnv.scala
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ import scala.collection.concurrent
import scala.collection.mutable
import scala.util.Properties

import com.google.common.cache.CacheBuilder
import com.github.benmanes.caffeine.cache.Caffeine
import org.apache.hadoop.conf.Configuration

import org.apache.spark.annotation.DeveloperApi
Expand Down Expand Up @@ -77,7 +77,7 @@ class SparkEnv (
// A general, soft-reference map for metadata needed during HadoopRDD split computation
// (e.g., HadoopFileRDD uses this to cache JobConfs and InputFormats).
private[spark] val hadoopJobMetadata =
CacheBuilder.newBuilder().softValues().build[String, AnyRef]().asMap()
Caffeine.newBuilder().softValues().build[String, AnyRef]().asMap()

private[spark] var driverTmpDir: Option[String] = None

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,9 @@ import javax.servlet.http.{HttpServletRequest, HttpServletResponse}
import scala.collection.JavaConverters._

import com.codahale.metrics.{Counter, MetricRegistry, Timer}
import com.google.common.cache.{CacheBuilder, CacheLoader, LoadingCache, RemovalListener, RemovalNotification}
import com.github.benmanes.caffeine.cache.{Caffeine, RemovalCause, RemovalListener}
import com.github.benmanes.caffeine.guava.CaffeinatedGuava
import com.google.common.cache.{CacheLoader, LoadingCache}
import com.google.common.util.concurrent.UncheckedExecutionException
import org.eclipse.jetty.servlet.FilterHolder

Expand Down Expand Up @@ -62,21 +64,27 @@ private[history] class ApplicationCache(

/**
* Removal event notifies the provider to detach the UI.
* @param rm removal notification
* @param key removal key
* @param value removal value
*/
override def onRemoval(rm: RemovalNotification[CacheKey, CacheEntry]): Unit = {
override def onRemoval(key: CacheKey, value: CacheEntry,
cause: RemovalCause): Unit = {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe mention RemovalCause in the scaladoc?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: move this to the previous line?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ok ~ It doesn't look exceeds 100 characters ~

metrics.evictionCount.inc()
val key = rm.getKey
logDebug(s"Evicting entry ${key}")
operations.detachSparkUI(key.appId, key.attemptId, rm.getValue().loadedUI.ui)
logDebug(s"Evicting entry $key")
operations.detachSparkUI(key.appId, key.attemptId, value.loadedUI.ui)
}
}

private val appCache: LoadingCache[CacheKey, CacheEntry] = {
CacheBuilder.newBuilder()
.maximumSize(retainedApplications)
.removalListener(removalListener)
.build(appLoader)
val builder = Caffeine.newBuilder()
.maximumSize(retainedApplications)
.removalListener(removalListener)
// SPARK-34309: Use custom Executor to compatible with
// the data eviction behavior of Guava cache
.executor((command: Runnable) => command.run())

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this going to run in the same thread? Is that what the old behaviour would have been?

@LuciferYang LuciferYang Jul 9, 2021

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, it's a compromise for compatibility with old behaviour at present. In the future, I will optimize this behaviour through other pr.

// Wrapping as CaffeinatedGuava to be compatible with
// the exception behavior of Guava cache
CaffeinatedGuava.build(builder, appLoader)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is there a reason to preserve behavior of guava cache here ?
If there are better alternatives, we should consider them.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

My uninformed review is that you can drop the guava adapters. The catch in get would be updated to instead unwrap an CompletionException if a checked exception was thrown by your cache loader.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks @ben-manes !
We should definitely evaluate alternatives - to understand both the behavior change (particularly if entirely internal to spark, like here) and performance impact.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we can remove the CaffeinatedGuava in the future, but it would be better to keep it now for two reasons:

  • For compatibility of exception handling behavior

For example, there are some differences in the processing of error, when the get method throws an Error, Guava cache wraps it as an ExecutionError and re-throw it, but Caffeine will re-throw Error directly. Different exception types and nesting levels may lead to some incompatibilities, such as SPARK-33587: isFatalError in ExecutorSuite, the test suite will fail if we don't use CaffeinatedGuava with Guava Cache Loader.

  • For compatibility of API usage

For example, SessionCatalog use V get(K key, Callable<? extends V> valueLoader) defined in com.google.common.cache.Cache, but there is no same interface in caffeine.cache.Cache.
In the future, maybe we can use use the V get(K key, Function<? super K, ? extends V> mappingFunction) method to replace it

}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,9 @@ import java.util.concurrent.TimeUnit
import scala.reflect.ClassTag
import scala.util.control.NonFatal

import com.google.common.cache.{CacheBuilder, CacheLoader}
import com.github.benmanes.caffeine.cache.Caffeine
import com.github.benmanes.caffeine.guava.CaffeinatedGuava
import com.google.common.cache.CacheLoader
import org.apache.hadoop.fs.Path

import org.apache.spark._
Expand Down Expand Up @@ -84,16 +86,18 @@ private[spark] class ReliableCheckpointRDD[T: ClassTag](
}

// Cache of preferred locations of checkpointed files.
@transient private[spark] lazy val cachedPreferredLocations = CacheBuilder.newBuilder()
.expireAfterWrite(
SparkEnv.get.conf.get(CACHE_CHECKPOINT_PREFERRED_LOCS_EXPIRE_TIME).get,
TimeUnit.MINUTES)
.build(
new CacheLoader[Partition, Seq[String]]() {
override def load(split: Partition): Seq[String] = {
getPartitionBlockLocations(split)
}
})
@transient private[spark] lazy val cachedPreferredLocations = {
val builder = Caffeine.newBuilder()
.expireAfterWrite(
SparkEnv.get.conf.get(CACHE_CHECKPOINT_PREFERRED_LOCS_EXPIRE_TIME).get,
TimeUnit.MINUTES)
val loader = new CacheLoader[Partition, Seq[String]]() {
override def load(split: Partition): Seq[String] = {
getPartitionBlockLocations(split)
}
}
CaffeinatedGuava.build(builder, loader)
}

// Returns the block locations of given partition on file system.
private def getPartitionBlockLocations(split: Partition): Seq[String] = {
Expand Down
17 changes: 11 additions & 6 deletions core/src/main/scala/org/apache/spark/storage/BlockManagerId.scala
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,9 @@ package org.apache.spark.storage

import java.io.{Externalizable, IOException, ObjectInput, ObjectOutput}

import com.google.common.cache.{CacheBuilder, CacheLoader}
import com.github.benmanes.caffeine.cache.Caffeine
import com.github.benmanes.caffeine.guava.CaffeinatedGuava
import com.google.common.cache.CacheLoader

import org.apache.spark.SparkContext
import org.apache.spark.annotation.DeveloperApi
Expand Down Expand Up @@ -136,11 +138,14 @@ private[spark] object BlockManagerId {
* The max cache size is hardcoded to 10000, since the size of a BlockManagerId
* object is about 48B, the total memory cost should be below 1MB which is feasible.
*/
val blockManagerIdCache = CacheBuilder.newBuilder()
.maximumSize(10000)
.build(new CacheLoader[BlockManagerId, BlockManagerId]() {
override def load(id: BlockManagerId) = id
})
val blockManagerIdCache = {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: maybe change this to:

  val blockManagerIdCache = Caffeine.newBuilder()
      .maximumSize(10000)
      .build[BlockManagerId, BlockManagerId](identity)

?

@LuciferYang LuciferYang Jul 27, 2021

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Similar to getPartitionBlockLocations , it cannot be compiled with .build[BlockManagerId, BlockManagerId](identity)

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

7b360a7 revert this change

val builder = Caffeine.newBuilder()
.maximumSize(10000)
CaffeinatedGuava.build(builder,
new CacheLoader[BlockManagerId, BlockManagerId]() {
override def load(id: BlockManagerId): BlockManagerId = id
})
}

def getCachedBlockManagerId(id: BlockManagerId): BlockManagerId = {
blockManagerIdCache.get(id)
Expand Down
21 changes: 13 additions & 8 deletions core/src/main/scala/org/apache/spark/util/Utils.scala
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,9 @@ import scala.util.control.{ControlThrowable, NonFatal}
import scala.util.matching.Regex

import _root_.io.netty.channel.unix.Errors.NativeIoException
import com.google.common.cache.{CacheBuilder, CacheLoader, LoadingCache}
import com.github.benmanes.caffeine.cache.Caffeine
import com.github.benmanes.caffeine.guava.CaffeinatedGuava
import com.google.common.cache.{CacheLoader, LoadingCache}
import com.google.common.io.{ByteStreams, Files => GFiles}
import com.google.common.net.InetAddresses
import org.apache.commons.codec.binary.Hex
Expand Down Expand Up @@ -1585,13 +1587,16 @@ private[spark] object Utils extends Logging {
if (compressedLogFileLengthCache == null) {
val compressedLogFileLengthCacheSize = sparkConf.get(
UNCOMPRESSED_LOG_FILE_LENGTH_CACHE_SIZE_CONF)
compressedLogFileLengthCache = CacheBuilder.newBuilder()
.maximumSize(compressedLogFileLengthCacheSize)
.build[String, java.lang.Long](new CacheLoader[String, java.lang.Long]() {
override def load(path: String): java.lang.Long = {
Utils.getCompressedFileLength(new File(path))
}
})
compressedLogFileLengthCache = {
val builder = Caffeine.newBuilder()
.maximumSize(compressedLogFileLengthCacheSize)
CaffeinatedGuava.build(builder,
new CacheLoader[String, java.lang.Long]() {
override def load(path: String): java.lang.Long = {
Utils.getCompressedFileLength(new File(path))
}
})
}
}
compressedLogFileLengthCache
}
Expand Down
94 changes: 94 additions & 0 deletions core/src/test/scala/org/apache/spark/LocalCacheBenchmark.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
/*
* 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 org.apache.spark

import scala.util.Random

import com.github.benmanes.caffeine.cache.{CacheLoader => CaffeineCacheLoader, Caffeine}
import com.github.benmanes.caffeine.guava.CaffeinatedGuava
import com.google.common.cache.{CacheBuilder, CacheLoader, LoadingCache}

import org.apache.spark.benchmark.{Benchmark, BenchmarkBase}

/**
* Benchmark for Guava Cache vs Caffeine.
* To run this benchmark:
* {{{
* 1. without sbt:
* bin/spark-submit --class <this class> --jars <spark core test jar>
* 2. build/sbt "core/test:runMain <this class>"
* 3. generate result:
* SPARK_GENERATE_BENCHMARK_FILES=1 build/sbt "core/test:runMain <this class>"
* Results will be written to "benchmarks/KryoBenchmark-results.txt".

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

update this

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done

* }}}
*/
object LocalCacheBenchmark extends BenchmarkBase {

override def runBenchmarkSuite(mainArgs: Array[String]): Unit = {
runBenchmark("Loading Cache") {
val size = 10000
val parallelism = 8
val guavaCacheConcurrencyLevel = 8
val dataset = (1 to parallelism)
.map(_ => Random.shuffle(List.range(0, size)))
.map(list => list.map(i => TestData(i)))
Comment on lines +50 to +52

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This distribution is uniformly distributed with only single key overlaps. This means that there are not hot and cold entries, e.g. random eviction has an optimal hit rate. In reality, some entries will be used much more often and follows a power law curve.

That is fairly generous distribution for a cache like guava, which uses coarse locking of multiple hash tables. That way the access distribution matches the hash distribution, so ideally spread across all of the locks. In reality, while the hash distribution will be uniform the access distribution is not so a lock holding hot entries will be used much more frequently.

In Caffeine's benchmarks, it uses a scrambled Zipfian distribution (YCSB's generator). That would show an even larger speedup.

More just an fyi that your benchmarks are conservative and you may see a larger gain. Of course, if the caches are not a bottleneck you might not see any benefit except if the eviction policy improves the hit rates in your workloads.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you for your advice. I think we should avoid introducing more dependencies, so I'll try to implement this data generator in spark code.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think your code is fine as is. Maybe just document the simplification? I mostly wanted to let you know since writing a good benchmark is hard, not that you should change it. Your code served its purpose, and you might not get much more out of improving it.

val guavaCacheLoader = new CacheLoader[TestData, TestData]() {
override def load(id: TestData): TestData = {
id
}
}
val caffeineCacheLoader = new CaffeineCacheLoader[TestData, TestData]() {
override def load(id: TestData): TestData = {
id
}
}

val benchmark = new Benchmark("Loading Cache", size * parallelism, 3, output = output)
benchmark.addCase("Guava Cache") { _ =>
val cache = CacheBuilder.newBuilder()
.concurrencyLevel(guavaCacheConcurrencyLevel).build[TestData, TestData](guavaCacheLoader)
dataset.par.foreach(dataList => dataList.foreach(key => cache.get(key)))
cache.cleanUp()
}

benchmark.addCase("Caffeine") { _ =>
val cache = Caffeine.newBuilder().build[TestData, TestData](caffeineCacheLoader)
dataset.par.foreach(dataList => dataList.foreach(key => cache.get(key)))
cache.cleanUp()
}

benchmark.addCase("CaffeinatedGuava with Guava CacheLoader") { _ =>
val cache: LoadingCache[TestData, TestData] =
CaffeinatedGuava.build(Caffeine.newBuilder(), guavaCacheLoader)
dataset.par.foreach(dataList => dataList.foreach(key => cache.get(key)))
cache.cleanUp()
}

benchmark.addCase("CaffeinatedGuava with Caffeine CacheLoader") { _ =>
val cache: LoadingCache[TestData, TestData] =
CaffeinatedGuava.build(Caffeine.newBuilder(), caffeineCacheLoader)
dataset.par.foreach(dataList => dataList.foreach(key => cache.get(key)))
cache.cleanUp()
}

benchmark.run()
}
}

case class TestData(content: Int)
}
Loading