Skip to content

Excessive allocations ExtraFactory.decorate #1421

@kilink

Description

@kilink

I frequently see ExtraFactory.decorate at the top of the list of allocation hotspots when I profile our services. Here is an example from a 1 minute profile:

image

It looks to me like this is caused by excessive resizing of the extras ArrayList. If we look at ExtraFactory.decorate, I believe this happens because of this line:

TraceContext.Builder builder = context.toBuilder().clearExtra().addExtra(claimed);

And then the subsequent loop that adds Extra instances one at a time; it's an interaction between the above code and Lists.ensureMutable; we always pass Collections.emptyList() to Lists.ensureMutable, which causes the ArrayList to get initialized with an initialCapacity of 0. Due to how Java's ArrayList calculates the new capacity when growing a list, for sizes this small we end up reallocating A LOT. Here is a simple example that demonstrates that:

TraceContext.Builder builder = TraceContext.newBuilder().clearExtra();
builder.addExtra("foo"); // ArrayList.grow called, capacity = 0, minCapacity = 1, newCapacity = 1
builder.addExtra("bar"); // ArrayList.grow called, capacity = 1, minCapacity = 2, newCapacity = 2
builder.addExtra("baz"); // ArrayList.grow called, capacity = 2, minCapacity = 3, newCapacity = 3
builder.addExtra("quux"); // ArrayList.grow called, capacity = 3, minCapacity = 4, newCapacity = 4
builder.addExtra("blah"); // ArrayList.grow called, capacity = 4, minCapacity = 5, newCapacity = 6
builder.addExtra("blahblah"); // ArrayList.grow not called, capacity = 6
builder.addExtra("blahblahblah"); // ArrayList.grow called, capacity=6, minCapacity = 7, newCapacity = 9

Basically, initializing an ArrayList with a small initial capacity (such as zero), and then adding items one-by-one will trigger excessive copying, since the first 5 element insertions trigger a resize. At that point we'e allocated in total 5 arrays, with a combined capacity of 15; we would have been better off initializing the original ArrayList with more headroom. One approach is to do just that in Lists.ensureMutable:

 public static <E> List<E> ensureMutable(List<E> list) {
  if (list instanceof ArrayList) return list;
  List<E> mutable = new ArrayList<>(Math.max(6, list.size())); // some suitable default, ArrayList uses 10
  mutable.addAll(list);
  return mutable;
}

Another option is to try to size the list more accurately in ExtraFactory.decorate, by using extraSize or something similar.

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions