Use FastThreadLocal for CodecOutputList

Motivation:

We used Recycler for the CodecOutputList which is not optimized for the use-case of access only from the same Thread all the time.

Modifications:

- Use FastThreadLocal for CodecOutputList
- Add benchmark

Result:

Less overhead in our codecs.
This commit is contained in:
Norman Maurer 2017-12-29 18:59:41 +01:00
parent 5c8450e215
commit 3b57a73602
2 changed files with 124 additions and 13 deletions

View File

@ -15,7 +15,8 @@
*/ */
package io.netty.handler.codec; package io.netty.handler.codec;
import io.netty.util.Recycler; import io.netty.util.concurrent.FastThreadLocal;
import io.netty.util.internal.MathUtil;
import java.util.AbstractList; import java.util.AbstractList;
import java.util.RandomAccess; import java.util.RandomAccess;
@ -27,25 +28,81 @@ import static io.netty.util.internal.ObjectUtil.checkNotNull;
*/ */
final class CodecOutputList extends AbstractList<Object> implements RandomAccess { final class CodecOutputList extends AbstractList<Object> implements RandomAccess {
private static final Recycler<CodecOutputList> RECYCLER = new Recycler<CodecOutputList>() { private static final CodecOutputListRecycler NOOP_RECYCLER = new CodecOutputListRecycler() {
@Override @Override
protected CodecOutputList newObject(Handle handle) { public void recycle(CodecOutputList object) {
return new CodecOutputList(handle); // drop on the floor and let the GC handle it.
} }
}; };
static CodecOutputList newInstance() { private static final FastThreadLocal<CodecOutputLists> CODEC_OUTPUT_LISTS_POOL =
return RECYCLER.get(); new FastThreadLocal<CodecOutputLists>() {
@Override
protected CodecOutputLists initialValue() throws Exception {
// 16 CodecOutputList per Thread are cached.
return new CodecOutputLists(16);
}
};
private interface CodecOutputListRecycler {
void recycle(CodecOutputList codecOutputList);
} }
private final Recycler.Handle handle; private static final class CodecOutputLists implements CodecOutputListRecycler {
private final CodecOutputList[] elements;
private final int mask;
private int currentIdx;
private int count;
CodecOutputLists(int numElements) {
elements = new CodecOutputList[MathUtil.safeFindNextPositivePowerOfTwo(numElements)];
for (int i = 0; i < elements.length; ++i) {
// Size of 16 should be good enough for the majority of all users as an initial capacity.
elements[i] = new CodecOutputList(this, 16);
}
count = elements.length;
currentIdx = elements.length;
mask = elements.length - 1;
}
public CodecOutputList getOrCreate() {
if (count == 0) {
// Return a new CodecOutputList which will not be cached. We use a size of 4 to keep the overhead
// low.
return new CodecOutputList(NOOP_RECYCLER, 4);
}
--count;
int idx = (currentIdx - 1) & mask;
CodecOutputList list = elements[idx];
currentIdx = idx;
return list;
}
@Override
public void recycle(CodecOutputList codecOutputList) {
int idx = currentIdx;
elements[idx] = codecOutputList;
currentIdx = (idx + 1) & mask;
++count;
assert count <= elements.length;
}
}
static CodecOutputList newInstance() {
return CODEC_OUTPUT_LISTS_POOL.get().getOrCreate();
}
private final CodecOutputListRecycler recycler;
private int size; private int size;
// Size of 16 should be good enough for 99 % of all users. private Object[] array;
private Object[] array = new Object[16];
private boolean insertSinceRecycled; private boolean insertSinceRecycled;
private CodecOutputList(Recycler.Handle handle) { private CodecOutputList(CodecOutputListRecycler recycler, int size) {
this.handle = handle; this.recycler = recycler;
array = new Object[size];
} }
@Override @Override
@ -135,9 +192,10 @@ final class CodecOutputList extends AbstractList<Object> implements RandomAccess
for (int i = 0 ; i < size; i ++) { for (int i = 0 ; i < size; i ++) {
array[i] = null; array[i] = null;
} }
clear(); size = 0;
insertSinceRecycled = false; insertSinceRecycled = false;
RECYCLER.recycle(this, handle);
recycler.recycle(this);
} }
/** /**

View File

@ -0,0 +1,53 @@
/*
* Copyright 2017 The Netty Project
*
* The Netty Project licenses this file to you under the Apache License,
* version 2.0 (the "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at:
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*/
package io.netty.handler.codec;
import io.netty.microbench.util.AbstractMicrobenchmark;
import org.openjdk.jmh.annotations.Benchmark;
import org.openjdk.jmh.annotations.Param;
import org.openjdk.jmh.annotations.Scope;
import org.openjdk.jmh.annotations.State;
import org.openjdk.jmh.annotations.Threads;
@State(Scope.Benchmark)
@Threads(16)
public class AdvancedCodecOutputListBenchmark extends AbstractMicrobenchmark {
private static final Object ELEMENT = new Object();
@Param({ "1", "4" })
public int elements;
@Benchmark
public boolean codecOutListAllocRecycle() {
return benchmark(elements, CodecOutputList.newInstance(), CodecOutputList.newInstance(),
CodecOutputList.newInstance(), CodecOutputList.newInstance());
}
private static boolean benchmark(int elements, CodecOutputList list1, CodecOutputList list2,
CodecOutputList list3, CodecOutputList list4) {
return (benchmark(elements, list1) == benchmark(elements, list2)) ==
(benchmark(elements, list3) == benchmark(elements, list4));
}
private static boolean benchmark(int elements, CodecOutputList list) {
for (int i = 0; i < elements; ++i) {
list.add(ELEMENT);
}
list.recycle();
return list.insertSinceRecycled();
}
}