Skip to content

Conversation

@JoshRosen
Copy link
Contributor

@JoshRosen JoshRosen commented May 16, 2017

What changes were proposed in this pull request?

In

./bin/spark-shell --master=local[64]

I ran

sc.parallelize(1 to 100000, 100000).count()

and profiled the time spend in the LiveListenerBus event processing thread. I discovered that the majority of the time was being spent in TaskMetrics.empty calls in JobProgressListener.onTaskStart. It turns out that we can slightly refactor to remove the need to construct one empty instance per call, greatly improving the performance of this code.

The performance gains here help to avoid an issue where listener events would be dropped because the JobProgressListener couldn't keep up with the throughput.

Before:

image

After:

image

How was this patch tested?

Benchmarks described above.



import InternalAccumulator._
@transient private[spark] lazy val nameToAccums = LinkedHashMap(
Copy link
Contributor Author

@JoshRosen JoshRosen May 16, 2017

Choose a reason for hiding this comment

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

It looks like the use of LinkedHashMap was added by @cloud-fan in #12612 in order to preserve ordering from the old code. As far as I can tell we don't actually rely on the ordering of the entries in this map, so I didn't preserved the use of LinkedHashMap.

testAccum.foreach { accum =>
map.put(TEST_ACCUM, accum)
}
map.asScala
Copy link
Contributor Author

Choose a reason for hiding this comment

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

The map + wrapper might consume a little bit of extra memory compared to the old code but it doesn't matter because we don't have that many TaskMetrics resident in the JVM at the same time: in the executor, the only instances are in TaskContexts and in the driver you only have one per stage in the scheduler and some temporary ones in the listener bus queue which are freed as soon as the queue events are processed (which happens faster now, outweighing the extra space usage).

@JoshRosen
Copy link
Contributor Author

Actually, stepping back a second, we might be able to completely remove this bottleneck by simply not constructing tons of empty TaskMetrics objects in JobProgressListener's hot path. Let me see if I can update to do that instead.

@JoshRosen JoshRosen changed the title [SPARK-20776] Fix perf. problems in TaskMetrics.nameToAccums map initialization [SPARK-20776] Fix perf. problems in JobProgressListener caused by TaskMetrics construction May 16, 2017
updateAggregateMetrics(stageData, info.executorId, m, oldMetrics)
}

val taskData = stageData.taskData.getOrElseUpdate(info.taskId, TaskUIData(info, None))
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Important note here: in the old code, the elseUpdate branch would only be taken in rare error cases where we somehow purged the TaskUIData which should have been created when the task launched. It technically doesn't matter what we put in for the Option[Metrics] here since it just gets unconditionally overwritten on line 410 in the old code. So while my new code constructs TaskUIData with default metrics it doesn't actually change the behavior of this block.

private var _metrics: Option[TaskMetricsUIData]) {
class TaskUIData private(private var _taskInfo: TaskInfo) {

private[this] var _metrics: Option[TaskMetricsUIData] = Some(TaskMetricsUIData.EMPTY)
Copy link
Contributor

Choose a reason for hiding this comment

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

when will this be None?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

The only way for this to become None is if updateTaskMetrics is called with None.

updateTaskMetrics is called in two places:

  • In JobProgressListener.onTaskEnd, where the metrics are from Option(taskEnd.taskMetrics), where taskEnd.taskMetrics can be null in case the task has failed (according to docs).
  • In JobProgressListener.onExecutorMetricsUpdate, where the metrics are guaranteed to be defined / non-None.

@cloud-fan
Copy link
Contributor

LGTM

@SparkQA
Copy link

SparkQA commented May 17, 2017

Test build #76984 has finished for PR 18008 at commit 4675b21.

  • This patch fails Spark unit tests.
  • This patch merges cleanly.
  • This patch adds no public classes.

@SparkQA
Copy link

SparkQA commented May 17, 2017

Test build #76988 has finished for PR 18008 at commit 6e66b80.

  • This patch passes all tests.
  • This patch merges cleanly.
  • This patch adds no public classes.

@SparkQA
Copy link

SparkQA commented May 17, 2017

Test build #76990 has finished for PR 18008 at commit 1c62909.

  • This patch passes all tests.
  • This patch merges cleanly.
  • This patch adds no public classes.

@SparkQA
Copy link

SparkQA commented May 17, 2017

Test build #76991 has finished for PR 18008 at commit feda785.

  • This patch passes all tests.
  • This patch merges cleanly.
  • This patch adds no public classes.

@cloud-fan
Copy link
Contributor

thanks, merging to master/2.2!

asfgit pushed a commit that referenced this pull request May 17, 2017
…kMetrics construction

## What changes were proposed in this pull request?

In

```
./bin/spark-shell --master=local[64]
```

I ran

```
sc.parallelize(1 to 100000, 100000).count()
```
and profiled the time spend in the LiveListenerBus event processing thread. I discovered that the majority of the time was being spent in `TaskMetrics.empty` calls in `JobProgressListener.onTaskStart`. It turns out that we can slightly refactor to remove the need to construct one empty instance per call, greatly improving the performance of this code.

The performance gains here help to avoid an issue where listener events would be dropped because the JobProgressListener couldn't keep up with the throughput.

**Before:**

![image](https://cloud.githubusercontent.com/assets/50748/26133095/95bcd42a-3a59-11e7-8051-a50550e447b8.png)

**After:**

![image](https://cloud.githubusercontent.com/assets/50748/26133070/7935e148-3a59-11e7-8c2d-73d5aa5a2397.png)

## How was this patch tested?

Benchmarks described above.

Author: Josh Rosen <[email protected]>

Closes #18008 from JoshRosen/nametoaccums-improvements.

(cherry picked from commit 30e0557)
Signed-off-by: Wenchen Fan <[email protected]>
@asfgit asfgit closed this in 30e0557 May 17, 2017
@JoshRosen JoshRosen deleted the nametoaccums-improvements branch May 17, 2017 05:07
@witgo
Copy link
Contributor

witgo commented May 17, 2017

@JoshRosen , what's the tool in your screenshot?

@JoshRosen
Copy link
Contributor Author

@witgo, I'm using YourKit Java Profiler 2016.02. In these screenshots I enabled CPU sampling then took a performance snapshot and used the per-thread view, focusing on the time taken in the live listener bus thread by right-clicking on the subtree and choosing "focus subtree" from the context menu.

@witgo
Copy link
Contributor

witgo commented May 17, 2017

@JoshRosen I see, Thank you.

robert3005 pushed a commit to palantir/spark that referenced this pull request May 19, 2017
…kMetrics construction

## What changes were proposed in this pull request?

In

```
./bin/spark-shell --master=local[64]
```

I ran

```
sc.parallelize(1 to 100000, 100000).count()
```
and profiled the time spend in the LiveListenerBus event processing thread. I discovered that the majority of the time was being spent in `TaskMetrics.empty` calls in `JobProgressListener.onTaskStart`. It turns out that we can slightly refactor to remove the need to construct one empty instance per call, greatly improving the performance of this code.

The performance gains here help to avoid an issue where listener events would be dropped because the JobProgressListener couldn't keep up with the throughput.

**Before:**

![image](https://cloud.githubusercontent.com/assets/50748/26133095/95bcd42a-3a59-11e7-8051-a50550e447b8.png)

**After:**

![image](https://cloud.githubusercontent.com/assets/50748/26133070/7935e148-3a59-11e7-8c2d-73d5aa5a2397.png)

## How was this patch tested?

Benchmarks described above.

Author: Josh Rosen <[email protected]>

Closes apache#18008 from JoshRosen/nametoaccums-improvements.
lycplus pushed a commit to lycplus/spark that referenced this pull request May 24, 2017
…kMetrics construction

## What changes were proposed in this pull request?

In

```
./bin/spark-shell --master=local[64]
```

I ran

```
sc.parallelize(1 to 100000, 100000).count()
```
and profiled the time spend in the LiveListenerBus event processing thread. I discovered that the majority of the time was being spent in `TaskMetrics.empty` calls in `JobProgressListener.onTaskStart`. It turns out that we can slightly refactor to remove the need to construct one empty instance per call, greatly improving the performance of this code.

The performance gains here help to avoid an issue where listener events would be dropped because the JobProgressListener couldn't keep up with the throughput.

**Before:**

![image](https://cloud.githubusercontent.com/assets/50748/26133095/95bcd42a-3a59-11e7-8051-a50550e447b8.png)

**After:**

![image](https://cloud.githubusercontent.com/assets/50748/26133070/7935e148-3a59-11e7-8c2d-73d5aa5a2397.png)

## How was this patch tested?

Benchmarks described above.

Author: Josh Rosen <[email protected]>

Closes apache#18008 from JoshRosen/nametoaccums-improvements.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants