Index: lucene/facet/src/java/org/apache/lucene/util/encoding/DGapSemiPackedEncoder.java
===================================================================
--- lucene/facet/src/java/org/apache/lucene/util/encoding/DGapSemiPackedEncoder.java	(revision 0)
+++ lucene/facet/src/java/org/apache/lucene/util/encoding/DGapSemiPackedEncoder.java	(working copy)
@@ -0,0 +1,146 @@
+package org.apache.lucene.util.encoding;
+
+import org.apache.lucene.util.ArrayUtil;
+import org.apache.lucene.util.BytesRef;
+import org.apache.lucene.util.IntsRef;
+import org.apache.lucene.util.RamUsageEstimator;
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF 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.
+ */
+
+/**
+ * An {@link IntEncoder} which implements variable length encoding for the gap
+ * between values, while paying the variable length price only for values greater than 255. 
+ * It's a specialized form of the combination of {@link DGapIntEncoder} and {@link SemiPackedEncoder}.
+ * 
+ * @see SemiPackedEncoder
+ * @see DGapIntEncoder
+ * 
+ * @lucene.experimental
+ */
+public class DGapSemiPackedEncoder extends IntEncoder {
+  
+  @Override
+  public void encode(IntsRef values, BytesRef buf) {
+    
+    buf.offset = buf.length = 0;
+    final int maxBytesNeeded = 6 * values.length; // at most 6 bytes, 5 for VInt, 1 for marker
+    if (buf.bytes.length < maxBytesNeeded) {
+      buf.grow(maxBytesNeeded);
+    }
+    
+    int idx = 0;
+    int prev = 0;
+    final byte[] bytes = buf.bytes;
+    for(int i = 0; i < values.length; i++) {
+      final int realValue = values.ints[values.offset+i];
+      final int v = realValue - prev;
+      prev = realValue;
+
+      // If it fits in a single byte
+      if ((v & ~0xFF) == 0) {
+        // write the byte directly
+        bytes[idx++] = (byte) (v & 0xFF);
+        continue;
+      }
+
+      // The value does not fit in a single byte - add a zero marker - the next value is in VINT form
+      bytes[idx++] = 0;
+
+      // TODO consider VInt(v-0xFF) instead VInt(v) - as v is guaranteed to be
+      // larger than 0xFF. That might save some space
+
+      // Encode v as VInt
+      if ((v & ~0x7F) == 0) {
+        bytes[idx++] = (byte) v;
+      } else if ((v & ~0x3FFF) == 0) {
+        bytes[idx++] = (byte) (0x80 | ((v & 0x3F80) >> 7));
+        bytes[idx++] = (byte) (v & 0x7F);
+      } else if ((v & ~0x1FFFFF) == 0) {
+        bytes[idx++] = (byte) (0x80 | ((v & 0x1FC000) >> 14));
+        bytes[idx++] = (byte) (0x80 | ((v & 0x3F80) >> 7));
+        bytes[idx++] = (byte) (v & 0x7F);
+      } else if ((v & ~0xFFFFFFF) == 0) {
+        bytes[idx++] = (byte) (0x80 | ((v & 0xFE00000) >> 21));
+        bytes[idx++] = (byte) (0x80 | ((v & 0x1FC000) >> 14));
+        bytes[idx++] = (byte) (0x80 | ((v & 0x3F80) >> 7));
+        bytes[idx++] = (byte) (v & 0x7F);
+      } else {
+        bytes[idx++] = (byte) (0x80 | ((v & 0xF0000000) >> 28));
+        bytes[idx++] = (byte) (0x80 | ((v & 0xFE00000) >> 21));
+        bytes[idx++] = (byte) (0x80 | ((v & 0x1FC000) >> 14));
+        bytes[idx++] = (byte) (0x80 | ((v & 0x3F80) >> 7));
+        bytes[idx++] = (byte) (v & 0x7F);
+      }
+      
+    }
+    buf.length = idx;
+  }
+  
+  @Override
+  public String toString() {
+    return "DGapSemiPacked";
+  }
+  
+  @Override
+  public IntDecoder createMatchingDecoder() {
+    return new IntDecoder() {
+      
+      @Override
+      public void decode(BytesRef buf, IntsRef values) {
+        values.offset = values.length = 0;
+
+        // grow the buffer up front, even if by a large number of values (buf.length)
+        // that saves the need to check inside the loop for every decoded value if
+        // the buffer needs to grow.
+        if (values.ints.length < buf.length) {
+          values.ints = new int[ArrayUtil.oversize(buf.length, RamUsageEstimator.NUM_BYTES_INT)];
+        }
+        
+        int prev = 0;
+        final int upto = buf.offset + buf.length;
+        final byte[] bytes = buf.bytes;
+        final int[] vals = values.ints;
+        int idx = buf.offset;
+        int valuesIdx = 0;
+        while (idx < upto) {
+          if (bytes[idx] == 0) {
+            // found zero marker - the value is decoded as VInt
+            ++idx;
+            int value = 0;
+            while (true) {
+              byte first = bytes[idx++];
+              value |= first & 0x7F;
+              if ((first & 0x80) == 0) {
+                prev += value;
+                vals[valuesIdx++] = prev;
+                break;
+              }
+              value <<= 7;
+            }
+            continue;
+          }
+          
+          // Did not find a zero marker - the value fits in this single byte
+          prev += 0xFF & bytes[idx++];
+          vals[valuesIdx++] = prev;
+        }
+        values.length = valuesIdx;
+      }
+    };
+  }
+}
Index: lucene/facet/src/java/org/apache/lucene/util/encoding/SemiPackedEncoder.java
===================================================================
--- lucene/facet/src/java/org/apache/lucene/util/encoding/SemiPackedEncoder.java	(revision 0)
+++ lucene/facet/src/java/org/apache/lucene/util/encoding/SemiPackedEncoder.java	(working copy)
@@ -0,0 +1,131 @@
+package org.apache.lucene.util.encoding;
+
+import org.apache.lucene.util.ArrayUtil;
+import org.apache.lucene.util.BytesRef;
+import org.apache.lucene.util.IntsRef;
+import org.apache.lucene.util.RamUsageEstimator;
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF 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.
+ */
+
+/**
+ * An {@link IntEncoder} which implements variable length encoding for the given
+ * values, while paying the variable length price only for values greater than
+ * 255. 
+ * 
+ * @lucene.experimental
+ */
+public class SemiPackedEncoder extends IntEncoder {
+  
+  @Override
+  public void encode(IntsRef values, BytesRef buf) {
+    
+    buf.offset = buf.length = 0;
+    final int maxBytesNeeded = 6 * values.length; // at most 6 bytes per VInt
+    if (buf.bytes.length < maxBytesNeeded) {
+      buf.grow(maxBytesNeeded);
+    }
+    
+    int idx = 0;
+    final byte[] bytes = buf.bytes;
+    for(int i = 0; i < values.length; i++) {
+      final int v = values.ints[values.offset+i];
+      // If the value fits in a single byte
+      if ((v & ~0xFF) == 0) {
+        // Write it as is
+        bytes[idx++] = (byte) (v & 0xFF);
+        continue;
+      }
+
+      // The value does not fit in a single byte - add a zero marker - the next value is in VINT form
+      bytes[idx++] = 0;
+
+      // TODO consider VInt(v-0xFF) instead VInt(v) - as v is guaranteed to be
+      // larger than 0xFF. That might save some space
+
+      // Encode v as VInt
+      if ((v & ~0x7F) == 0) {
+        bytes[idx++] = (byte) v;
+      } else if ((v & ~0x3FFF) == 0) {
+        bytes[idx++] = (byte) (0x80 | ((v & 0x3F80) >> 7));
+        bytes[idx++] = (byte) (v & 0x7F);
+      } else if ((v & ~0x1FFFFF) == 0) {
+        bytes[idx++] = (byte) (0x80 | ((v & 0x1FC000) >> 14));
+        bytes[idx++] = (byte) (0x80 | ((v & 0x3F80) >> 7));
+        bytes[idx++] = (byte) (v & 0x7F);
+      } else if ((v & ~0xFFFFFFF) == 0) {
+        bytes[idx++] = (byte) (0x80 | ((v & 0xFE00000) >> 21));
+        bytes[idx++] = (byte) (0x80 | ((v & 0x1FC000) >> 14));
+        bytes[idx++] = (byte) (0x80 | ((v & 0x3F80) >> 7));
+        bytes[idx++] = (byte) (v & 0x7F);
+      } else {
+        bytes[idx++] = (byte) (0x80 | ((v & 0xF0000000) >> 28));
+        bytes[idx++] = (byte) (0x80 | ((v & 0xFE00000) >> 21));
+        bytes[idx++] = (byte) (0x80 | ((v & 0x1FC000) >> 14));
+        bytes[idx++] = (byte) (0x80 | ((v & 0x3F80) >> 7));
+        bytes[idx++] = (byte) (v & 0x7F);
+      }    }
+    buf.length = idx;
+  }
+  
+  @Override
+  public String toString() {
+    return "SemiPacked";
+  }
+  
+  @Override
+  public IntDecoder createMatchingDecoder() {
+    return new IntDecoder() {
+      
+      @Override
+      public void decode(BytesRef buf, IntsRef values) {
+        values.offset = values.length = 0;
+
+        // grow the buffer up front, even if by a large number of values (buf.length)
+        // that saves the need to check inside the loop for every decoded value if
+        // the buffer needs to grow.
+        if (values.ints.length < buf.length) {
+          values.ints = new int[ArrayUtil.oversize(buf.length, RamUsageEstimator.NUM_BYTES_INT)];
+        }
+        
+        final int upto = buf.offset + buf.length;
+        final byte[] bytes = buf.bytes;
+        final int[] vals = values.ints;
+        int idx = buf.offset;
+        while (idx < upto) {
+          if (bytes[idx] == 0) {
+            // Found zero marker, the value is decoded as VInt
+            ++idx;
+            int value = 0;
+            while (true) {
+              byte first = bytes[idx++];
+              value |= first & 0x7F;
+              if ((first & 0x80) == 0) {
+                vals[values.length++] = value;
+                break;
+              }
+              value <<= 7;
+            }
+            continue;
+          }
+          // Did not find a zero marker - the value fits in this single byte
+          vals[values.length++] = 0xFF & bytes[idx++];
+        }
+      }
+    };
+  }
+}
Index: lucene/facet/src/test/org/apache/lucene/util/encoding/EncodingSpeed.java
===================================================================
--- lucene/facet/src/test/org/apache/lucene/util/encoding/EncodingSpeed.java	(revision 1443529)
+++ lucene/facet/src/test/org/apache/lucene/util/encoding/EncodingSpeed.java	(working copy)
@@ -78,6 +78,8 @@
     encoderTest(new SortingIntEncoder(new UniqueValuesIntEncoder(new VInt8IntEncoder())), facetIDs, loopFactor);
     encoderTest(new SortingIntEncoder(new UniqueValuesIntEncoder(new DGapIntEncoder(new VInt8IntEncoder()))), facetIDs, loopFactor);
     encoderTest(new SortingIntEncoder(new UniqueValuesIntEncoder(new DGapVInt8IntEncoder())), facetIDs, loopFactor);
+    encoderTest(new SortingIntEncoder(new UniqueValuesIntEncoder(new DGapIntEncoder(new SemiPackedEncoder()))), facetIDs, loopFactor);
+    encoderTest(new SortingIntEncoder(new UniqueValuesIntEncoder(new DGapSemiPackedEncoder())), facetIDs, loopFactor);
     encoderTest(new SortingIntEncoder(new UniqueValuesIntEncoder(new DGapIntEncoder(new EightFlagsIntEncoder()))), facetIDs, loopFactor);
     encoderTest(new SortingIntEncoder(new UniqueValuesIntEncoder(new DGapIntEncoder(new FourFlagsIntEncoder()))), facetIDs, loopFactor);
     encoderTest(new SortingIntEncoder(new UniqueValuesIntEncoder(new DGapIntEncoder(new NOnesIntEncoder(3)))), facetIDs, loopFactor);
@@ -123,6 +125,9 @@
     }
     
     if (decoded.length != values.length) {
+      Arrays.sort(values);
+      System.out.println(Arrays.toString(values));
+      System.out.println(Arrays.toString(Arrays.copyOf(decoded.ints, decoded.length)));
       throw new RuntimeException("wrong num values. expected=" + values.length + " actual=" + decoded.length + 
           " decoder=" + decoder);
     }
