Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Consider to remove sorting at MeterIdPrefix #2150

Open
imasahiro opened this issue Oct 3, 2019 · 8 comments
Open

Consider to remove sorting at MeterIdPrefix #2150

imasahiro opened this issue Oct 3, 2019 · 8 comments

Comments

@imasahiro
Copy link
Member

Currently, we sort the micrometer tags at MeterIdPrefix to create Meter ID stably.
https://github.com/line/armeria/blob/master/core/src/main/java/com/linecorp/armeria/common/metric/MeterIdPrefix.java#L95-L101

Since I saw this stacktrace from our service’s flamegraph, I would like to consider whether it is avoidable or not. Since it doesn’t have many samples, we can be ignored but because it is not optimal, it would be happy if we can avoid this sort.

Screen Shot 2019-10-03 at 13 11 03

@trustin
Copy link
Member

trustin commented Oct 4, 2019

We can make use of the following characteristics:

  • The number of tag names is very small (less than 10 usually)
  • We always construct a new sorted tag list because MeterIdPrefix is immutable.
  • A user may have specified tags in a sorted form already, i.e. sort could be done quickly if the input is sorted already.
  • When merging two tag lists:
    • the first tag list is always sorted.
    • the second tag list is also very small.

@anuraaga
Copy link
Collaborator

anuraaga commented Oct 4, 2019

I don't think this would be an issue if we sorted during metric export instead of when recording metrics.

Do you think it's possible to move sorting to a different layer that only occurs during export?

@trustin
Copy link
Member

trustin commented Oct 4, 2019

That's a good point. We could:

  • Sort only when creating a meter.
  • Make MeterIdPrefix.equals() and hashCode() order-independent.

@anuraaga
Copy link
Collaborator

anuraaga commented Oct 7, 2019

I played with this a little and found

  • Micrometer sorts before creating a meter anyways, so we don't need to worry about it. Removing sorting still allows prometheus integration test to pass even though it adds tags out-of-order

  • We need to do order-independent hashcode/equals for the memoizing that happens in MicrometerUtil.register. However, I don't know if it's possible to do this faster than the current sort. I tried these three techniques, all slower than HEAD, and in this order of performance. Do you know of any other trick for unordered Equals that might be faster than the sort we do?

    • this.tags.containsAll(that.tags) && that.tags.containsAll(this.tags) (probably fast right now since by default we don't have that many tags)
    • Store field this.unorderedTags = ImmutableSet.copyOf(tags), this.unorderedTags.equals(that.unorderedTags)
    • ImmutableSet.copyOf(this.tags).equals(ImmutableSet.copyOf(that.tags));

Also of the optimizations in

#2150 (comment)

I guess the one that might have an affect is doing insertion sort for merging - TimSort already essentially skips sorted input so I don't know if we're able to beat it by taking already-sortedness into account.

Let me now if you have any thoughts.

@anuraaga
Copy link
Collaborator

anuraaga commented Oct 7, 2019

By the way, @imasahiro are you using default MeterIdPrefixFunctionFactory.DEFAULT or do you add your own tags with MeterIdPrefixFunction.withTags? If latter, how many tags?

@imasahiro
Copy link
Member Author

@anuraaga Yes, basically, I'm using armeria-spring's default MeterIdPrefixFunctionFactory.DEFAULT.

@trustin
Copy link
Member

trustin commented Oct 7, 2019

Wouldn't order-indepdendent hashCode() with memoization help a lot since it will make full comparison skip in many cases? i.e. order-independent equals() will be slower but hashCode() will cover the loss to some extent.

@anuraaga
Copy link
Collaborator

anuraaga commented Oct 7, 2019

The benchmark in my PR does the prefix generation+register but I didn't see it cover the difference unfortunately. I guess it would require very slow sorting (adding largish number of tags in very random order) to see the difference so maybe we can live with it now since default function can be made to add the tags in order.

trustin pushed a commit that referenced this issue Oct 7, 2019
This is the smallest solution for helping with the performance of tag addition. Our default logic can at least add tags in order to prevent reordering when sorting (TimSort handles already-sorted arrays well without special logic).

Also removes comparator when sorting since `Tag` is `Comparable` and adds tests for `Equals/HashCode` for use in playing with those functions.

For #2150

After
```
Benchmark                                        Mode  Cnt        Score         Error  Units
RequestMetricSupportBenchmark.registerSameTags  thrpt    5  6816146.723 ▒ 1548641.211  ops/s
```

Before
```
Benchmark                                        Mode  Cnt        Score        Error  Units
RequestMetricSupportBenchmark.registerSameTags  thrpt    5  5813367.536 ▒ 154548.384  ops/s
```
eugene70 pushed a commit to eugene70/armeria that referenced this issue Oct 16, 2019
This is the smallest solution for helping with the performance of tag addition. Our default logic can at least add tags in order to prevent reordering when sorting (TimSort handles already-sorted arrays well without special logic).

Also removes comparator when sorting since `Tag` is `Comparable` and adds tests for `Equals/HashCode` for use in playing with those functions.

For line#2150

After
```
Benchmark                                        Mode  Cnt        Score         Error  Units
RequestMetricSupportBenchmark.registerSameTags  thrpt    5  6816146.723 ▒ 1548641.211  ops/s
```

Before
```
Benchmark                                        Mode  Cnt        Score        Error  Units
RequestMetricSupportBenchmark.registerSameTags  thrpt    5  5813367.536 ▒ 154548.384  ops/s
```
fmguerreiro pushed a commit to fmguerreiro/armeria that referenced this issue Sep 19, 2020
This is the smallest solution for helping with the performance of tag addition. Our default logic can at least add tags in order to prevent reordering when sorting (TimSort handles already-sorted arrays well without special logic).

Also removes comparator when sorting since `Tag` is `Comparable` and adds tests for `Equals/HashCode` for use in playing with those functions.

For line#2150

After
```
Benchmark                                        Mode  Cnt        Score         Error  Units
RequestMetricSupportBenchmark.registerSameTags  thrpt    5  6816146.723 ▒ 1548641.211  ops/s
```

Before
```
Benchmark                                        Mode  Cnt        Score        Error  Units
RequestMetricSupportBenchmark.registerSameTags  thrpt    5  5813367.536 ▒ 154548.384  ops/s
```
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

4 participants