You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@tajo.apache.org by ji...@apache.org on 2015/03/24 09:39:51 UTC

[2/4] tajo git commit: TAJO-1407: Minor performance improvement of MemSortExec.

TAJO-1407: Minor performance improvement of MemSortExec.

Closes #426

Signed-off-by: Jihoon Son <ji...@apache.org>


Project: http://git-wip-us.apache.org/repos/asf/tajo/repo
Commit: http://git-wip-us.apache.org/repos/asf/tajo/commit/d7b5212c
Tree: http://git-wip-us.apache.org/repos/asf/tajo/tree/d7b5212c
Diff: http://git-wip-us.apache.org/repos/asf/tajo/diff/d7b5212c

Branch: refs/heads/index_support
Commit: d7b5212ceb569ba145318d78cff46ca49bef973c
Parents: a921585
Author: Jihoon Son <ji...@apache.org>
Authored: Tue Mar 24 17:09:20 2015 +0900
Committer: Jihoon Son <ji...@apache.org>
Committed: Tue Mar 24 17:09:20 2015 +0900

----------------------------------------------------------------------
 CHANGES                                         |   3 +
 .../org/apache/tajo/datum/BooleanDatum.java     |   9 +-
 .../org/apache/tajo/datum/DatumFactory.java     |   4 +
 .../java/org/apache/tajo/datum/TextDatum.java   |   4 +-
 .../engine/planner/physical/MemSortExec.java    |  28 ++-
 .../tajo/engine/planner/physical/SortExec.java  |   4 +-
 .../engine/planner/physical/TupleSorter.java    |  27 +++
 .../planner/physical/VectorizedSorter.java      | 198 +++++++++++++++++++
 .../planner/physical/TestTupleSorter.java       | 132 +++++++++++++
 9 files changed, 389 insertions(+), 20 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/tajo/blob/d7b5212c/CHANGES
----------------------------------------------------------------------
diff --git a/CHANGES b/CHANGES
index 811992c..58c061a 100644
--- a/CHANGES
+++ b/CHANGES
@@ -9,6 +9,9 @@ Release 0.11.0 - unreleased
 
   IMPROVEMENT
 
+    TAJO-1407: Minor performance improvement of MemSortExec. (Contributed by
+    navis, Committed by jihoon)
+
     TAJO-1403: Improve 'Simple Query' with only partition columns and constant 
     values. (Contributed by Dongjoon Hyun, Committed by jihoon)
 

http://git-wip-us.apache.org/repos/asf/tajo/blob/d7b5212c/tajo-common/src/main/java/org/apache/tajo/datum/BooleanDatum.java
----------------------------------------------------------------------
diff --git a/tajo-common/src/main/java/org/apache/tajo/datum/BooleanDatum.java b/tajo-common/src/main/java/org/apache/tajo/datum/BooleanDatum.java
index 93933a8..596540f 100644
--- a/tajo-common/src/main/java/org/apache/tajo/datum/BooleanDatum.java
+++ b/tajo-common/src/main/java/org/apache/tajo/datum/BooleanDatum.java
@@ -18,6 +18,7 @@
 
 package org.apache.tajo.datum;
 
+import com.google.common.primitives.Booleans;
 import com.google.gson.annotations.Expose;
 import org.apache.tajo.common.TajoDataTypes;
 import org.apache.tajo.exception.InvalidOperationException;
@@ -167,13 +168,7 @@ public class BooleanDatum extends Datum {
   public int compareTo(Datum datum) {
     switch (datum.type()) {
     case BOOLEAN:
-      if (val && !datum.asBool()) {
-        return -1;
-      } else if (val && datum.asBool()) {
-        return 1;
-      } else {
-        return 0;
-      }
+      return Booleans.compare(val, datum.asBool());
     default:
       throw new InvalidOperationException(datum.type());
     }

http://git-wip-us.apache.org/repos/asf/tajo/blob/d7b5212c/tajo-common/src/main/java/org/apache/tajo/datum/DatumFactory.java
----------------------------------------------------------------------
diff --git a/tajo-common/src/main/java/org/apache/tajo/datum/DatumFactory.java b/tajo-common/src/main/java/org/apache/tajo/datum/DatumFactory.java
index 11ba791..9f48cad 100644
--- a/tajo-common/src/main/java/org/apache/tajo/datum/DatumFactory.java
+++ b/tajo-common/src/main/java/org/apache/tajo/datum/DatumFactory.java
@@ -317,6 +317,10 @@ public class DatumFactory {
     return new IntervalDatum(interval);
   }
 
+  public static IntervalDatum createInterval(int month, long interval) {
+    return new IntervalDatum(month, interval);
+  }
+
   public static DateDatum createDate(Datum datum) {
     switch (datum.type()) {
     case INT4:

http://git-wip-us.apache.org/repos/asf/tajo/blob/d7b5212c/tajo-common/src/main/java/org/apache/tajo/datum/TextDatum.java
----------------------------------------------------------------------
diff --git a/tajo-common/src/main/java/org/apache/tajo/datum/TextDatum.java b/tajo-common/src/main/java/org/apache/tajo/datum/TextDatum.java
index ba45e71..ffd6ca2 100644
--- a/tajo-common/src/main/java/org/apache/tajo/datum/TextDatum.java
+++ b/tajo-common/src/main/java/org/apache/tajo/datum/TextDatum.java
@@ -33,7 +33,6 @@ import java.util.Comparator;
 public class TextDatum extends Datum {
   public static final Charset DEFAULT_CHARSET = Charset.forName("UTF-8");
 
-  @Expose private final int size;
   /* encoded in UTF-8 */
   @Expose private final byte[] bytes;
 
@@ -44,7 +43,6 @@ public class TextDatum extends Datum {
   public TextDatum(byte[] bytes) {
     super(TajoDataTypes.Type.TEXT);
     this.bytes = bytes;
-    this.size = bytes.length;
   }
 
   public TextDatum(String string) {
@@ -108,7 +106,7 @@ public class TextDatum extends Datum {
 
   @Override
   public int size() {
-    return size;
+    return bytes.length;
   }
 
   @Override

http://git-wip-us.apache.org/repos/asf/tajo/blob/d7b5212c/tajo-core/src/main/java/org/apache/tajo/engine/planner/physical/MemSortExec.java
----------------------------------------------------------------------
diff --git a/tajo-core/src/main/java/org/apache/tajo/engine/planner/physical/MemSortExec.java b/tajo-core/src/main/java/org/apache/tajo/engine/planner/physical/MemSortExec.java
index c77313e..a2e039c 100644
--- a/tajo-core/src/main/java/org/apache/tajo/engine/planner/physical/MemSortExec.java
+++ b/tajo-core/src/main/java/org/apache/tajo/engine/planner/physical/MemSortExec.java
@@ -29,12 +29,12 @@ import java.util.Collections;
 import java.util.Iterator;
 import java.util.List;
 
-public class MemSortExec extends SortExec {
+public class MemSortExec extends SortExec implements TupleSorter {
   private SortNode plan;
   private List<Tuple> tupleSlots;
   private boolean sorted = false;
   private Iterator<Tuple> iterator;
-  
+
   public MemSortExec(final TaskAttemptContext context,
                      SortNode plan, PhysicalExec child) {
     super(context, plan.getInSchema(), plan.getOutSchema(), child, plan.getSortKeys());
@@ -43,7 +43,7 @@ public class MemSortExec extends SortExec {
 
   public void init() throws IOException {
     super.init();
-    this.tupleSlots = new ArrayList<Tuple>(1000);
+    this.tupleSlots = new ArrayList<Tuple>(10000);
   }
 
   @Override
@@ -54,12 +54,10 @@ public class MemSortExec extends SortExec {
       while (!context.isStopped() && (tuple = child.next()) != null) {
         tupleSlots.add(new VTuple(tuple));
       }
-      
-      Collections.sort(tupleSlots, getComparator());
-      this.iterator = tupleSlots.iterator();
+      iterator = getSorter().sort();
       sorted = true;
     }
-    
+
     if (iterator.hasNext()) {
       return this.iterator.next();
     } else {
@@ -67,6 +65,14 @@ public class MemSortExec extends SortExec {
     }
   }
 
+  private TupleSorter getSorter() {
+    try {
+      return new VectorizedSorter(tupleSlots, sortSpecs, comparator.getSortKeyIds());
+    } catch (Exception e) {
+      return this;
+    }
+  }
+
   @Override
   public void rescan() throws IOException {
     super.rescan();
@@ -86,4 +92,10 @@ public class MemSortExec extends SortExec {
   public SortNode getPlan() {
     return this.plan;
   }
-}
+
+  @Override
+  public Iterator<Tuple> sort() {
+    Collections.sort(tupleSlots, comparator);
+    return tupleSlots.iterator();
+  }
+}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/tajo/blob/d7b5212c/tajo-core/src/main/java/org/apache/tajo/engine/planner/physical/SortExec.java
----------------------------------------------------------------------
diff --git a/tajo-core/src/main/java/org/apache/tajo/engine/planner/physical/SortExec.java b/tajo-core/src/main/java/org/apache/tajo/engine/planner/physical/SortExec.java
index c0703dd..fb6a3b2 100644
--- a/tajo-core/src/main/java/org/apache/tajo/engine/planner/physical/SortExec.java
+++ b/tajo-core/src/main/java/org/apache/tajo/engine/planner/physical/SortExec.java
@@ -29,8 +29,8 @@ import java.io.IOException;
 import java.util.Comparator;
 
 public abstract class SortExec extends UnaryPhysicalExec {
-  private final TupleComparator comparator;
-  private final SortSpec [] sortSpecs;
+  protected final BaseTupleComparator comparator;
+  protected final SortSpec [] sortSpecs;
 
   public SortExec(TaskAttemptContext context, Schema inSchema,
                   Schema outSchema, PhysicalExec child, SortSpec [] sortSpecs) {

http://git-wip-us.apache.org/repos/asf/tajo/blob/d7b5212c/tajo-core/src/main/java/org/apache/tajo/engine/planner/physical/TupleSorter.java
----------------------------------------------------------------------
diff --git a/tajo-core/src/main/java/org/apache/tajo/engine/planner/physical/TupleSorter.java b/tajo-core/src/main/java/org/apache/tajo/engine/planner/physical/TupleSorter.java
new file mode 100644
index 0000000..d240e4a
--- /dev/null
+++ b/tajo-core/src/main/java/org/apache/tajo/engine/planner/physical/TupleSorter.java
@@ -0,0 +1,27 @@
+/**
+ * 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.
+ */
+
+package org.apache.tajo.engine.planner.physical;
+
+import org.apache.tajo.storage.Tuple;
+
+import java.util.Iterator;
+
+public interface TupleSorter {
+  Iterator<Tuple> sort();
+}

http://git-wip-us.apache.org/repos/asf/tajo/blob/d7b5212c/tajo-core/src/main/java/org/apache/tajo/engine/planner/physical/VectorizedSorter.java
----------------------------------------------------------------------
diff --git a/tajo-core/src/main/java/org/apache/tajo/engine/planner/physical/VectorizedSorter.java b/tajo-core/src/main/java/org/apache/tajo/engine/planner/physical/VectorizedSorter.java
new file mode 100644
index 0000000..891d104
--- /dev/null
+++ b/tajo-core/src/main/java/org/apache/tajo/engine/planner/physical/VectorizedSorter.java
@@ -0,0 +1,198 @@
+/**
+ * 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.
+ */
+
+package org.apache.tajo.engine.planner.physical;
+
+import com.google.common.primitives.Booleans;
+import com.google.common.primitives.Doubles;
+import com.google.common.primitives.Floats;
+import com.google.common.primitives.Ints;
+import com.google.common.primitives.Longs;
+import com.google.common.primitives.Shorts;
+import org.apache.hadoop.util.IndexedSortable;
+import org.apache.hadoop.util.QuickSort;
+import org.apache.tajo.catalog.SortSpec;
+import org.apache.tajo.common.TajoDataTypes;
+import org.apache.tajo.datum.Datum;
+import org.apache.tajo.datum.TextDatum;
+import org.apache.tajo.exception.UnsupportedException;
+import org.apache.tajo.storage.Tuple;
+
+import java.util.BitSet;
+import java.util.Iterator;
+import java.util.List;
+
+/**
+ * Extract raw level values (primitive or String/byte[]) from each of key columns before sorting
+ * Uses indirection for efficient swapping
+ */
+public class VectorizedSorter implements IndexedSortable, TupleSorter {
+
+  private final Tuple[] tuples;         // source tuples
+  private final TupleVector[] vectors;  // values of key columns
+  private final int[] mappings;         // index indirection
+
+  public VectorizedSorter(List<Tuple> source, SortSpec[] sortKeys, int[] keyIndex) {
+    this.tuples = source.toArray(new Tuple[source.size()]);
+    vectors = new TupleVector[sortKeys.length];
+    mappings = new int[tuples.length];
+    for (int i = 0; i < vectors.length; i++) {
+      TajoDataTypes.Type type = sortKeys[i].getSortKey().getDataType().getType();
+      boolean nullFirst = sortKeys[i].isNullFirst();
+      boolean ascending = sortKeys[i].isAscending();
+      boolean nullInvert = nullFirst && ascending || !nullFirst && !ascending;
+      vectors[i] = new TupleVector(TupleVector.getType(type), tuples.length, nullInvert, ascending);
+    }
+    for (int i = 0; i < tuples.length; i++) {
+      for (int j = 0; j < keyIndex.length; j++) {
+        vectors[j].add(tuples[i].get(keyIndex[j]));
+      }
+      mappings[i] = i;
+    }
+  }
+
+  @Override
+  public int compare(int i1, int i2) {
+    final int index1 = mappings[i1];
+    final int index2 = mappings[i2];
+    for (TupleVector vector : vectors) {
+      int compare = vector.compare(index1, index2);
+      if (compare != 0) {
+        return compare;
+      }
+    }
+    return 0;
+  }
+
+  @Override
+  public void swap(int i1, int i2) {
+    int v1 = mappings[i1];
+    mappings[i1] = mappings[i2];
+    mappings[i2] = v1;
+  }
+
+  @Override
+  public Iterator<Tuple> sort() {
+    new QuickSort().sort(VectorizedSorter.this, 0, mappings.length);
+    return new Iterator<Tuple>() {
+      int index;
+      public boolean hasNext() { return index < mappings.length; }
+      public Tuple next() { return tuples[mappings[index++]]; }
+      public void remove() { throw new UnsupportedException(); }
+    };
+  }
+
+  private static class TupleVector {
+
+    private final int type;
+    private final BitSet nulls;
+    private final boolean nullInvert;
+    private final boolean ascending;
+
+    private boolean[] booleans;
+    private byte[] bits;
+    private short[] shorts;
+    private int[] ints;
+    private long[] longs;
+    private float[] floats;
+    private double[] doubles;
+    private byte[][] bytes;
+
+    private int index;
+
+    private TupleVector(int type, int length, boolean nullInvert, boolean ascending) {
+      this.type = type;
+      this.nulls = new BitSet(length);
+      this.nullInvert = nullInvert;
+      this.ascending = ascending;
+      switch (type) {
+        case 0: booleans = new boolean[length]; break;
+        case 1: bits = new byte[length]; break;
+        case 2: shorts = new short[length]; break;
+        case 3: ints = new int[length]; break;
+        case 4: longs = new long[length]; break;
+        case 5: floats = new float[length]; break;
+        case 6: doubles = new double[length]; break;
+        case 7: bytes = new byte[length][]; break;
+        default:
+          throw new IllegalArgumentException();
+      }
+    }
+
+    private void add(Datum datum) {
+      if (datum.isNull()) {
+        nulls.set(index++);
+        return;
+      }
+      switch (type) {
+        case 0: booleans[index] = datum.asBool(); break;
+        case 1: bits[index] = datum.asByte(); break;
+        case 2: shorts[index] = datum.asInt2(); break;
+        case 3: ints[index] = datum.asInt4(); break;
+        case 4: longs[index] = datum.asInt8(); break;
+        case 5: floats[index] = datum.asFloat4(); break;
+        case 6: doubles[index] = datum.asFloat8(); break;
+        case 7: bytes[index] = datum.asByteArray(); break;
+        default:
+          throw new IllegalArgumentException();
+      }
+      index++;
+    }
+
+    private int compare(int index1, int index2) {
+      final boolean n1 = nulls.get(index1);
+      final boolean n2 = nulls.get(index2);
+      if (n1 && n2) {
+        return 0;
+      }
+      if (n1 ^ n2) {
+        int compVal = n1 ? 1 : -1;
+        return nullInvert ? -compVal : compVal;
+      }
+      int compare;
+      switch (type) {
+        case 0: compare = Booleans.compare(booleans[index1], booleans[index2]); break;
+        case 1: compare = bits[index1] - bits[index2]; break;
+        case 2: compare = Shorts.compare(shorts[index1], shorts[index2]); break;
+        case 3: compare = Ints.compare(ints[index1], ints[index2]); break;
+        case 4: compare = Longs.compare(longs[index1], longs[index2]); break;
+        case 5: compare = Floats.compare(floats[index1], floats[index2]); break;
+        case 6: compare = Doubles.compare(doubles[index1], doubles[index2]); break;
+        case 7: compare = TextDatum.COMPARATOR.compare(bytes[index1], bytes[index2]); break;
+        default:
+          throw new IllegalArgumentException();
+      }
+      return ascending ? compare : -compare;
+    }
+
+    public static int getType(TajoDataTypes.Type type) {
+      switch (type) {
+        case BOOLEAN: return 0;
+        case BIT: case INT1: return 1;
+        case INT2: return 2;
+        case INT4: case DATE: case INET4: return 3;
+        case INT8: case TIME: case TIMESTAMP: case INTERVAL: return 4;
+        case FLOAT4: return 5;
+        case FLOAT8: return 6;
+        case TEXT: case CHAR: case BLOB: return 7;
+      }
+      // todo
+      throw new UnsupportedException(type.name());
+    }
+  }
+}

http://git-wip-us.apache.org/repos/asf/tajo/blob/d7b5212c/tajo-core/src/test/java/org/apache/tajo/engine/planner/physical/TestTupleSorter.java
----------------------------------------------------------------------
diff --git a/tajo-core/src/test/java/org/apache/tajo/engine/planner/physical/TestTupleSorter.java b/tajo-core/src/test/java/org/apache/tajo/engine/planner/physical/TestTupleSorter.java
new file mode 100644
index 0000000..fc43d42
--- /dev/null
+++ b/tajo-core/src/test/java/org/apache/tajo/engine/planner/physical/TestTupleSorter.java
@@ -0,0 +1,132 @@
+/**
+ * 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.
+ */
+
+package org.apache.tajo.engine.planner.physical;
+
+import com.google.common.collect.Iterators;
+import com.google.common.primitives.Ints;
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+import org.apache.tajo.catalog.Column;
+import org.apache.tajo.catalog.Schema;
+import org.apache.tajo.catalog.SortSpec;
+import org.apache.tajo.common.TajoDataTypes.Type;
+import org.apache.tajo.datum.Datum;
+import org.apache.tajo.datum.DatumFactory;
+import org.apache.tajo.storage.BaseTupleComparator;
+import org.apache.tajo.storage.Tuple;
+import org.apache.tajo.storage.VTuple;
+import org.junit.Test;
+
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Random;
+import java.util.Set;
+import java.util.TreeSet;
+
+import static org.junit.Assert.assertArrayEquals;
+
+public class TestTupleSorter {
+
+  private static final Log LOG = LogFactory.getLog(TestTupleSorter.class);
+
+  private static final Random rnd = new Random(-1);
+
+  @Test
+  public final void testSortBench() {
+    final int MAX_SORT_KEY = 3;
+    final int ITERATION = 10;
+    final int LENGTH = 1000000;
+    final int SAMPLING = 100;
+
+    Tuple[] tuples = new Tuple[LENGTH];
+    for (int i = 0; i < LENGTH; i++) {
+      Datum[] datums = new Datum[]{
+          DatumFactory.createInt4(rnd.nextInt(Short.MAX_VALUE)),
+          DatumFactory.createInt4(rnd.nextInt()),
+          DatumFactory.createText("dept_" + rnd.nextInt()),
+          DatumFactory.createBool(rnd.nextBoolean()),
+          DatumFactory.createInt8(rnd.nextLong()),
+          DatumFactory.createInterval(rnd.nextInt(), rnd.nextLong())};
+      tuples[i] = new VTuple(datums);
+    }
+
+    Column col0 = new Column("col0", Type.INT2);
+    Column col1 = new Column("col1", Type.INT4);
+    Column col2 = new Column("col2", Type.TEXT);
+    Column col3 = new Column("col3", Type.BOOLEAN);
+    Column col4 = new Column("col4", Type.INT8);
+    Column col5 = new Column("col5", Type.INTERVAL);
+
+    Schema schema = new Schema(new Column[] {col0, col1, col2, col3, col4, col5});
+
+    long[] time1 = new long[ITERATION];
+    long[] time2 = new long[ITERATION];
+    for(int iteration = 0; iteration < ITERATION; iteration++) {
+      List<Tuple> target = Arrays.asList(Arrays.copyOf(tuples, tuples.length));
+      Set<Integer> keys = new TreeSet<Integer>();
+      for (int i = 0; i < MAX_SORT_KEY; i++) {
+        keys.add(rnd.nextInt(schema.size()));
+      }
+      int[] keyIndices = Ints.toArray(keys);
+      SortSpec[] sortKeys = new SortSpec[keyIndices.length];
+      for (int i = 0; i < keyIndices.length; i++) {
+        sortKeys[i] = new SortSpec(schema.getColumn(keyIndices[i]), rnd.nextBoolean(), rnd.nextBoolean());
+      }
+
+      long start = System.currentTimeMillis();
+      VectorizedSorter sorter = new VectorizedSorter(target, sortKeys, keyIndices);
+      Iterator<Tuple> iterator = sorter.sort();
+
+      String[] result1 = new String[SAMPLING];
+      for (int i = 0; i < result1.length; i++) {
+        Tuple tuple = Iterators.get(iterator, LENGTH / result1.length - 1);
+        StringBuilder builder = new StringBuilder();
+        for (int keyIndex : keyIndices) {
+          builder.append(tuple.get(keyIndex).asChars());
+        }
+        result1[i] = builder.toString();
+      }
+      time1[iteration] = System.currentTimeMillis() - start;
+
+      BaseTupleComparator comparator = new BaseTupleComparator(schema, sortKeys);
+
+      start = System.currentTimeMillis();
+      Collections.sort(target, comparator);
+      iterator = target.iterator();
+
+      String[] result2 = new String[SAMPLING];
+      for (int i = 0; i < result2.length; i++) {
+        Tuple tuple = Iterators.get(iterator, LENGTH / result2.length - 1);
+        StringBuilder builder = new StringBuilder();
+        for (int keyIndex : keyIndices) {
+          builder.append(tuple.get(keyIndex).asChars());
+        }
+        result2[i] = builder.toString();
+      }
+      time2[iteration] = System.currentTimeMillis() - start;
+
+      LOG.info("Sort on keys " + Arrays.toString(keyIndices) +
+          ": Vectorized " + time1[iteration]+ " msec, Original " + time2[iteration] + " msec");
+
+      assertArrayEquals(result1, result2);
+    }
+  }
+}