View Javadoc
1   /*
2    * Copyright 2012 The Netty Project
3    *
4    * The Netty Project licenses this file to you under the Apache License,
5    * version 2.0 (the "License"); you may not use this file except in compliance
6    * with the License. You may obtain a copy of the License at:
7    *
8    *   http://www.apache.org/licenses/LICENSE-2.0
9    *
10   * Unless required by applicable law or agreed to in writing, software
11   * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
12   * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
13   * License for the specific language governing permissions and limitations
14   * under the License.
15   */
16  package io.netty.channel;
17  
18  import io.netty.buffer.ByteBuf;
19  import io.netty.buffer.ByteBufAllocator;
20  
21  import java.util.ArrayList;
22  import java.util.List;
23  
24  /**
25   * The {@link RecvByteBufAllocator} that automatically increases and
26   * decreases the predicted buffer size on feed back.
27   * <p>
28   * It gradually increases the expected number of readable bytes if the previous
29   * read fully filled the allocated buffer.  It gradually decreases the expected
30   * number of readable bytes if the read operation was not able to fill a certain
31   * amount of the allocated buffer two times consecutively.  Otherwise, it keeps
32   * returning the same prediction.
33   */
34  public class AdaptiveRecvByteBufAllocator implements RecvByteBufAllocator {
35  
36      static final int DEFAULT_MINIMUM = 64;
37      static final int DEFAULT_INITIAL = 1024;
38      static final int DEFAULT_MAXIMUM = 65536;
39  
40      private static final int INDEX_INCREMENT = 4;
41      private static final int INDEX_DECREMENT = 1;
42  
43      private static final int[] SIZE_TABLE;
44  
45      static {
46          List<Integer> sizeTable = new ArrayList<Integer>();
47          for (int i = 16; i < 512; i += 16) {
48              sizeTable.add(i);
49          }
50  
51          for (int i = 512; i > 0; i <<= 1) {
52              sizeTable.add(i);
53          }
54  
55          SIZE_TABLE = new int[sizeTable.size()];
56          for (int i = 0; i < SIZE_TABLE.length; i ++) {
57              SIZE_TABLE[i] = sizeTable.get(i);
58          }
59      }
60  
61      public static final AdaptiveRecvByteBufAllocator DEFAULT = new AdaptiveRecvByteBufAllocator();
62  
63      private static int getSizeTableIndex(final int size) {
64          for (int low = 0, high = SIZE_TABLE.length - 1;;) {
65              if (high < low) {
66                  return low;
67              }
68              if (high == low) {
69                  return high;
70              }
71  
72              int mid = low + high >>> 1;
73              int a = SIZE_TABLE[mid];
74              int b = SIZE_TABLE[mid + 1];
75              if (size > b) {
76                  low = mid + 1;
77              } else if (size < a) {
78                  high = mid - 1;
79              } else if (size == a) {
80                  return mid;
81              } else {
82                  return mid + 1;
83              }
84          }
85      }
86  
87      private static final class HandleImpl implements Handle {
88          private final int minIndex;
89          private final int maxIndex;
90          private int index;
91          private int nextReceiveBufferSize;
92          private boolean decreaseNow;
93  
94          HandleImpl(int minIndex, int maxIndex, int initial) {
95              this.minIndex = minIndex;
96              this.maxIndex = maxIndex;
97  
98              index = getSizeTableIndex(initial);
99              nextReceiveBufferSize = SIZE_TABLE[index];
100         }
101 
102         @Override
103         public ByteBuf allocate(ByteBufAllocator alloc) {
104             return alloc.ioBuffer(nextReceiveBufferSize);
105         }
106 
107         @Override
108         public int guess() {
109             return nextReceiveBufferSize;
110         }
111 
112         @Override
113         public void record(int actualReadBytes) {
114             if (actualReadBytes <= SIZE_TABLE[Math.max(0, index - INDEX_DECREMENT - 1)]) {
115                 if (decreaseNow) {
116                     index = Math.max(index - INDEX_DECREMENT, minIndex);
117                     nextReceiveBufferSize = SIZE_TABLE[index];
118                     decreaseNow = false;
119                 } else {
120                     decreaseNow = true;
121                 }
122             } else if (actualReadBytes >= nextReceiveBufferSize) {
123                 index = Math.min(index + INDEX_INCREMENT, maxIndex);
124                 nextReceiveBufferSize = SIZE_TABLE[index];
125                 decreaseNow = false;
126             }
127         }
128     }
129 
130     private final int minIndex;
131     private final int maxIndex;
132     private final int initial;
133 
134     /**
135      * Creates a new predictor with the default parameters.  With the default
136      * parameters, the expected buffer size starts from {@code 1024}, does not
137      * go down below {@code 64}, and does not go up above {@code 65536}.
138      */
139     private AdaptiveRecvByteBufAllocator() {
140         this(DEFAULT_MINIMUM, DEFAULT_INITIAL, DEFAULT_MAXIMUM);
141     }
142 
143     /**
144      * Creates a new predictor with the specified parameters.
145      *
146      * @param minimum  the inclusive lower bound of the expected buffer size
147      * @param initial  the initial buffer size when no feed back was received
148      * @param maximum  the inclusive upper bound of the expected buffer size
149      */
150     public AdaptiveRecvByteBufAllocator(int minimum, int initial, int maximum) {
151         if (minimum <= 0) {
152             throw new IllegalArgumentException("minimum: " + minimum);
153         }
154         if (initial < minimum) {
155             throw new IllegalArgumentException("initial: " + initial);
156         }
157         if (maximum < initial) {
158             throw new IllegalArgumentException("maximum: " + maximum);
159         }
160 
161         int minIndex = getSizeTableIndex(minimum);
162         if (SIZE_TABLE[minIndex] < minimum) {
163             this.minIndex = minIndex + 1;
164         } else {
165             this.minIndex = minIndex;
166         }
167 
168         int maxIndex = getSizeTableIndex(maximum);
169         if (SIZE_TABLE[maxIndex] > maximum) {
170             this.maxIndex = maxIndex - 1;
171         } else {
172             this.maxIndex = maxIndex;
173         }
174 
175         this.initial = initial;
176     }
177 
178     @Override
179     public Handle newHandle() {
180         return new HandleImpl(minIndex, maxIndex, initial);
181     }
182 }