Skip to content

Commit

Permalink
Optimised Tags merge.
Browse files Browse the repository at this point in the history
  • Loading branch information
mstyura committed Sep 12, 2024
1 parent e87ee9e commit 18708d3
Show file tree
Hide file tree
Showing 3 changed files with 187 additions and 38 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
*/
package io.micrometer.benchmark.core;

import io.micrometer.core.instrument.Tag;
import io.micrometer.core.instrument.Tags;
import org.openjdk.jmh.annotations.*;
import org.openjdk.jmh.runner.Runner;
Expand All @@ -31,6 +32,60 @@
@OutputTimeUnit(TimeUnit.NANOSECONDS)
public class TagsBenchmark {

static final Tag[] orderedTagsSet10 = new Tag[] { Tag.of("key0", "value"), Tag.of("key1", "value"),
Tag.of("key2", "value"), Tag.of("key3", "value"), Tag.of("key4", "value"), Tag.of("key5", "value"),
Tag.of("key6", "value"), Tag.of("key7", "value"), Tag.of("key8", "value"), Tag.of("key9", "value") };

static final Tag[] orderedTagsSet4 = new Tag[] { Tag.of("key0", "value"), Tag.of("key1", "value"),
Tag.of("key2", "value"), Tag.of("key3", "value"), };

static final Tag[] orderedTagsSet2 = new Tag[] { Tag.of("key0", "value"), Tag.of("key1", "value"), };

static final Tag[] unorderedTagsSet10 = new Tag[] { Tag.of("key1", "value"), Tag.of("key2", "value"),
Tag.of("key3", "value"), Tag.of("key4", "value"), Tag.of("key5", "value"), Tag.of("key6", "value"),
Tag.of("key7", "value"), Tag.of("key8", "value"), Tag.of("key9", "value"), Tag.of("key0", "value") };

static final Tag[] unorderedTagsSet4 = new Tag[] { Tag.of("key1", "value"), Tag.of("key2", "value"),
Tag.of("key3", "value"), Tag.of("key0", "value"), };

static final Tag[] unorderedTagsSet2 = new Tag[] { Tag.of("key1", "value"), Tag.of("key0", "value") };

@Threads(1)
@Benchmark
public Tags tagsOfOrderedTagsSet10() {
return Tags.of(orderedTagsSet10);
}

@Threads(1)
@Benchmark
public Tags tagsOfOrderedTagsSet4() {
return Tags.of(orderedTagsSet4);
}

@Threads(1)
@Benchmark
public Tags tagsOfOrderedTagsSet2() {
return Tags.of(orderedTagsSet2);
}

@Threads(1)
@Benchmark
public Tags tagsOfUnorderedTagsSet10() {
return Tags.of(unorderedTagsSet10);
}

@Threads(1)
@Benchmark
public Tags tagsOfUnorderedTagsSet4() {
return Tags.of(unorderedTagsSet4);
}

@Threads(1)
@Benchmark
public Tags tagsOfUnorderedTagsSet2() {
return Tags.of(unorderedTagsSet2);
}

@Threads(16)
@Benchmark
public void of() {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright 2018 VMware, Inc.
* Copyright 2024 VMware, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
Expand Down Expand Up @@ -32,14 +32,14 @@
@State(Scope.Thread)
public class TagsMergeBenchmark {

Tags left = Tags.of("key", "value", "key2", "value2", "key6", "value6", "key7", "value7", "key8", "value8", "keyA",
"valueA", "keyC", "valueC", "keyE", "valueE", "keyF", "valueF", "keyG", "valueG", "keyG", "valueG", "keyG",
"valueG", "keyH", "valueH");
static final Tags left = Tags.of("key", "value", "key2", "value2", "key6", "value6", "key7", "value7", "key8",
"value8", "keyA", "valueA", "keyC", "valueC", "keyE", "valueE", "keyF", "valueF", "keyG", "valueG", "keyG",
"valueG", "keyG", "valueG", "keyH", "valueH");

Tags right = Tags.of("key", "value", "key1", "value1", "key2", "value2", "key3", "value3", "key4", "value4", "key5",
"value5", "keyA", "valueA", "keyB", "valueB", "keyD", "valueD");
static final Tags right = Tags.of("key", "value", "key1", "value1", "key2", "value2", "key3", "value3", "key4",
"value4", "key5", "value5", "keyA", "valueA", "keyB", "valueB", "keyD", "valueD");

@Threads(16)
@Threads(1)
@Benchmark
public Tags mergeTags() {
return left.and(right);
Expand Down
156 changes: 125 additions & 31 deletions micrometer-core/src/main/java/io/micrometer/core/instrument/Tags.java
Original file line number Diff line number Diff line change
Expand Up @@ -34,24 +34,76 @@
*/
public final class Tags implements Iterable<Tag> {

private static final Tags EMPTY = new Tags(new Tag[] {});
private static final Tags EMPTY = new Tags(new Tag[] {}, 0);

private final Tag[] tags;
/**
* A private array of {@code Tag} objects containing the sorted and deduplicated tags.
*/
private final Tag[] sortedSet;

/**
* The number of valid tags present in the {@link #sortedSet} array.
*/
private final int length;

/**
* A private constructor that initializes a {@code Tags} object with a sorted set of
* tags and its length.
* @param sortedSet an ordered set of unique tags by key
* @param length the number of valid tags in the {@code sortedSet}
*/
private Tags(Tag[] sortedSet, int length) {
this.sortedSet = sortedSet;
this.length = length;
}

private int last;
/**
* Checks if the first {@code length} elements of the {@code tags} array form an
* ordered set of tags.
* @param tags an array of tags.
* @param length the number of items to check.
* @return {@code true} if the first {@code length} items of {@code tags} form an
* ordered set; otherwise {@code false}.
*/
private static boolean isSortedSet(Tag[] tags, int length) {
if (length > tags.length) {
return false;
}
for (int i = 0; i < length - 1; i++) {
int cmp = tags[i].compareTo(tags[i + 1]);
if (cmp >= 0) {
return false;
}
}
return true;
}

private Tags(Tag[] tags) {
this.tags = tags;
Arrays.sort(this.tags);
dedup();
/**
* Constructs a {@code Tags} collection from the provided array of tags.
* @param tags an array of {@code Tag} objects, possibly unordered and/or containing
* duplicates.
* @return a {@code Tags} instance with a deduplicated and ordered set of tags.
*/
private static Tags make(Tag[] tags) {
int len = tags.length;
if (!isSortedSet(tags, len)) {
Arrays.sort(tags);
len = dedup(tags);
}
return new Tags(tags, len);
}

private void dedup() {
/**
* Removes duplicate tags from an ordered array of tags.
* @param tags an ordered array of {@code Tag} objects.
* @return the number of unique tags in the {@code tags} array after removing
* duplicates.
*/
private static int dedup(Tag[] tags) {
int n = tags.length;

if (n == 0 || n == 1) {
last = n;
return;
return n;
}

// index of next unique element
Expand All @@ -62,7 +114,53 @@ private void dedup() {
tags[j++] = tags[i];

tags[j++] = tags[n - 1];
last = j;
return j;
}

/**
* Constructs a {@code Tags} instance by merging two sets of tags in time proportional
* to the sum of their sizes.
* @param other the set of tags to merge with this one.
* @return a {@code Tags} instance with the merged sets of tags.
*/
private Tags merged(Tags other) {
if (other.length == 0) {
return this;
}
if (Objects.equals(this, other)) {
return this;
}
Tag[] sortedSet = new Tag[this.length + other.length];
int sortedIdx = 0, thisIdx = 0, otherIdx = 0;
while (thisIdx < this.length && otherIdx < other.length) {
int cmp = this.sortedSet[thisIdx].compareTo(other.sortedSet[otherIdx]);
if (cmp > 0) {
sortedSet[sortedIdx] = other.sortedSet[otherIdx];
otherIdx++;
}
else if (cmp < 0) {
sortedSet[sortedIdx] = this.sortedSet[thisIdx];
thisIdx++;
}
else {
// In case of key conflict prefer tag from other set
sortedSet[sortedIdx] = other.sortedSet[otherIdx];
thisIdx++;
otherIdx++;
}
sortedIdx++;
}
int thisRemaining = this.length - thisIdx;
if (thisRemaining > 0) {
System.arraycopy(this.sortedSet, thisIdx, sortedSet, sortedIdx, thisRemaining);
sortedIdx += thisRemaining;
}
int otherRemaining = other.length - otherIdx;
if (otherIdx < other.sortedSet.length) {
System.arraycopy(other.sortedSet, otherIdx, sortedSet, sortedIdx, otherRemaining);
sortedIdx += otherRemaining;
}
return new Tags(sortedSet, sortedIdx);
}

/**
Expand Down Expand Up @@ -99,10 +197,7 @@ public Tags and(@Nullable Tag... tags) {
if (blankVarargs(tags)) {
return this;
}
Tag[] newTags = new Tag[last + tags.length];
System.arraycopy(this.tags, 0, newTags, 0, last);
System.arraycopy(tags, 0, newTags, last, tags.length);
return new Tags(newTags);
return and(make(tags));
}

/**
Expand All @@ -116,11 +211,10 @@ public Tags and(@Nullable Iterable<? extends Tag> tags) {
return this;
}

if (this.tags.length == 0) {
if (this.length == 0) {
return Tags.of(tags);
}

return and(Tags.of(tags).tags);
return merged(Tags.of(tags));
}

@Override
Expand All @@ -134,12 +228,12 @@ private class ArrayIterator implements Iterator<Tag> {

@Override
public boolean hasNext() {
return currentIndex < last;
return currentIndex < length;
}

@Override
public Tag next() {
return tags[currentIndex++];
return sortedSet[currentIndex++];
}

@Override
Expand All @@ -151,7 +245,7 @@ public void remove() {

@Override
public Spliterator<Tag> spliterator() {
return Spliterators.spliterator(tags, 0, last, Spliterator.IMMUTABLE | Spliterator.ORDERED
return Spliterators.spliterator(sortedSet, 0, length, Spliterator.IMMUTABLE | Spliterator.ORDERED
| Spliterator.DISTINCT | Spliterator.NONNULL | Spliterator.SORTED);
}

Expand All @@ -166,8 +260,8 @@ public Stream<Tag> stream() {
@Override
public int hashCode() {
int result = 1;
for (int i = 0; i < last; i++) {
result = 31 * result + tags[i].hashCode();
for (int i = 0; i < length; i++) {
result = 31 * result + sortedSet[i].hashCode();
}
return result;
}
Expand All @@ -178,14 +272,14 @@ public boolean equals(@Nullable Object obj) {
}

private boolean tagsEqual(Tags obj) {
if (tags == obj.tags)
if (sortedSet == obj.sortedSet)
return true;

if (last != obj.last)
if (length != obj.length)
return false;

for (int i = 0; i < last; i++) {
if (!tags[i].equals(obj.tags[i]))
for (int i = 0; i < length; i++) {
if (!sortedSet[i].equals(obj.sortedSet[i]))
return false;
}

Expand Down Expand Up @@ -229,10 +323,10 @@ else if (tags instanceof Tags) {
}
else if (tags instanceof Collection) {
Collection<? extends Tag> tagsCollection = (Collection<? extends Tag>) tags;
return new Tags(tagsCollection.toArray(new Tag[0]));
return make(tagsCollection.toArray(new Tag[0]));
}
else {
return new Tags(StreamSupport.stream(tags.spliterator(), false).toArray(Tag[]::new));
return make(StreamSupport.stream(tags.spliterator(), false).toArray(Tag[]::new));
}
}

Expand All @@ -244,7 +338,7 @@ else if (tags instanceof Collection) {
* @return a new {@code Tags} instance
*/
public static Tags of(String key, String value) {
return new Tags(new Tag[] { Tag.of(key, value) });
return new Tags(new Tag[] { Tag.of(key, value) }, 1);
}

/**
Expand All @@ -264,7 +358,7 @@ public static Tags of(@Nullable String... keyValues) {
for (int i = 0; i < keyValues.length; i += 2) {
tags[i / 2] = Tag.of(keyValues[i], keyValues[i + 1]);
}
return new Tags(tags);
return make(tags);
}

private static boolean blankVarargs(@Nullable Object[] args) {
Expand Down

0 comments on commit 18708d3

Please sign in to comment.