public Dex merge() throws IOException {
    if (dexes.length == 1) {
      return dexes[0];
    } else if (dexes.length == 0) {
      return null;
    }

    long start = System.nanoTime();
    Dex result = mergeDexes();

    /*
     * We use pessimistic sizes when merging dex files. If those sizes
     * result in too many bytes wasted, compact the result. To compact,
     * simply merge the result with itself.
     */
    WriterSizes compactedSizes = new WriterSizes(this);
    int wastedByteCount = writerSizes.size() - compactedSizes.size();
    if (wastedByteCount > +compactWasteThreshold) {
      DexMerger compacter =
          new DexMerger(
              new Dex[] {dexOut, new Dex(0)}, CollisionPolicy.FAIL, context, compactedSizes);
      result = compacter.mergeDexes();
      context.out.printf(
          "Result compacted from %.1fKiB to %.1fKiB to save %.1fKiB%n",
          dexOut.getLength() / 1024f, result.getLength() / 1024f, wastedByteCount / 1024f);
    }

    long elapsed = System.nanoTime() - start;
    for (int i = 0; i < dexes.length; i++) {
      context.out.printf(
          "Merged dex #%d (%d defs/%.1fKiB)%n",
          i + 1, dexes[i].getTableOfContents().classDefs.size, dexes[i].getLength() / 1024f);
    }
    context.out.printf(
        "Result is %d defs/%.1fKiB. Took %.1fs%n",
        result.getTableOfContents().classDefs.size,
        result.getLength() / 1024f,
        elapsed / 1000000000f);

    return result;
  }
minimum number of wasted bytes before it's worthwhile to compact the result
android/android_framework/dex-maker/src/main/java/com/android/dx/merge/DexMerger.java