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 ac33ae9 commit 05512c2
Showing 1 changed file with 125 additions and 31 deletions.
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 05512c2

Please sign in to comment.