Przeglądaj źródła

Allow perf harness to compare multiple revisions of SwiftProtobuf (#426)

* Separate perf harness generator and runner scripts.
* Update perf harness to compare revisions.
* Remove commented code; add checkout cleanup hook.
Tony Allevato 9 lat temu
rodzic
commit
bd1e20ef96

+ 1 - 1
Performance/css/harness-visualization.css

@@ -16,7 +16,7 @@ table.numeric td {
 
 table.numeric th {
   background-color: #eee;
-  text-align: right;
+  text-align: center;
 }
 
 table.numeric tfoot td {

+ 1 - 32
Performance/perf_runner_cpp.sh → Performance/generators/cpp.sh

@@ -1,6 +1,6 @@
 #!/bin/bash
 
-# SwiftProtobuf/Performance/perf_runner_cpp.sh - C++ test harness generator
+# SwiftProtobuf/Performance/generators/cpp.sh - C++ test harness generator
 #
 # This source file is part of the Swift.org open source project
 #
@@ -16,8 +16,6 @@
 #
 # -----------------------------------------------------------------------------
 
-set -eu
-
 function print_cpp_set_field() {
   num=$1
   type=$2
@@ -157,32 +155,3 @@ EOF
 }
 EOF
 }
-
-function run_cpp_harness() {
-  harness="$1"
-
-  echo "Generating C++ harness source..."
-  gen_harness_path="$script_dir/_generated/Harness+Generated.cc"
-  generate_cpp_harness "$field_count" "$field_type"
-
-  echo "Building C++ test harness..."
-  time ( g++ --std=c++11 -O \
-      -o "$harness" \
-      -I "$script_dir" \
-      -I "$GOOGLE_PROTOBUF_CHECKOUT/src" \
-      -L "$GOOGLE_PROTOBUF_CHECKOUT/src/.libs" \
-      -lprotobuf \
-      "$gen_harness_path" \
-      "$script_dir/Harness.cc" \
-      "$script_dir/_generated/message.pb.cc" \
-      "$script_dir/main.cc" \
-  )
-  echo
-
-  # Make sure the dylib is loadable from the harness if the user hasn't
-  # actually installed them.
-  cp "$GOOGLE_PROTOBUF_CHECKOUT"/src/.libs/libprotobuf.*.dylib \
-      "$script_dir/_generated"
-
-  run_harness_and_concatenate_results "C++" "$harness" "$partial_results"
-}

+ 3 - 43
Performance/perf_runner_swift.sh → Performance/generators/swift.sh

@@ -1,6 +1,6 @@
 #!/bin/bash
 
-# SwiftProtobuf/Performance/perf_runner_swift.sh - Swift test harness generator
+# SwiftProtobuf/Performance/generators/swift.sh - Swift test harness generator
 #
 # This source file is part of the Swift.org open source project
 #
@@ -16,8 +16,6 @@
 #
 # -----------------------------------------------------------------------------
 
-set -eu
-
 function print_swift_set_field() {
   num=$1
   type=$2
@@ -86,8 +84,8 @@ extension Harness {
       }
 
       // Exercise text serialization.
-      let text = try measureSubtask("Encode text") {
-        return try message.textFormatString()
+      let text = measureSubtask("Encode text") {
+        return message.textFormatString()
       }
       _ = try measureSubtask("Decode text") {
         return try PerfMessage(textFormatString: text)
@@ -114,41 +112,3 @@ EOF
 }
 EOF
 }
-
-function run_swift_harness() {
-  harness="$1"
-
-  echo "Generating Swift harness source..."
-  gen_harness_path="$script_dir/_generated/Harness+Generated.swift"
-  generate_swift_harness "$field_count" "$field_type"
-
-  # Build the dynamic library to use in the tests from the .o files that were
-  # already built for the plug-in.
-  echo "Building SwiftProtobuf dynamic library..."
-  swiftc -emit-library -O -wmo -target x86_64-apple-macosx10.10 \
-      -o "$script_dir/_generated/libSwiftProtobuf.dylib" \
-      "$script_dir/../Sources/SwiftProtobuf/"*.swift
-
-  echo "Building Swift test harness..."
-  time ( swiftc -O -target x86_64-apple-macosx10.10 \
-      -o "$harness" \
-      -I "$script_dir/../.build/release" \
-      -L "$script_dir/_generated" \
-      -lSwiftProtobuf \
-      "$gen_harness_path" \
-      "$script_dir/Harness.swift" \
-      "$script_dir/_generated/message.pb.swift" \
-      "$script_dir/main.swift"
-  )
-  echo
-
-  dylib="$script_dir/_generated/libSwiftProtobuf.dylib"
-  echo "Swift dylib size before stripping: $(stat -f "%z" "$dylib") bytes"
-  cp "$dylib" "${dylib}_stripped"
-  strip -u -r "${dylib}_stripped"
-  echo "Swift dylib size after stripping:  $(stat -f "%z" "${dylib}_stripped") bytes"
-  echo
-
-  run_harness_and_concatenate_results "Swift" "$harness_swift" "$partial_results"
-  profile_harness "Swift" "$harness_swift"
-}

+ 21 - 36
Performance/js/harness-visualization.js

@@ -9,10 +9,6 @@
     'Equality',
   ];
 
-  // The languages we have harnesses for. The results for each language will
-  // appear as a series in the plot.
-  var languages = ['Swift', 'C++'];
-
   // The harnessSize keys we want to print in the summary table, in the order
   // they should be displayed.
   var harnessSizeKeys = ['Unstripped', 'Stripped'];
@@ -50,7 +46,7 @@
   };
 
   // Creates and return a series for the given language's results in a session.
-  function createSeries(session, language) {
+  function createSeries(session, series) {
     var x = [];
     var y = [];
 
@@ -59,7 +55,7 @@
     // vertical, which is what we want.
     for (var i = 0; i < benchmarks.length; i++) {
       var benchmark = benchmarks[i];
-      var timings = session[language][benchmark];
+      var timings = series.data[benchmark];
       if (timings) {
         for (var j = 0; j < timings.length; j++) {
           x.push(benchmark.replace(" ", "<br>"));
@@ -69,7 +65,7 @@
     }
 
     return {
-      name: language,
+      name: series.name,
       x: x,
       y: y,
       type: 'box',
@@ -126,10 +122,8 @@
     // Insert the runtime stats.
     var header = $('<tr></tr>').appendTo(table);
     header.append($('<th>Median runtimes</th>'));
-    for (var j = 0; j < languages.length; j++) {
-      header.append($('<th></th>').text(languages[j]));
-      header.append($('<th></th>'));
-      header.append($('<th></th>'));
+    for (var j = 0; j < session.series.length; j++) {
+      header.append($('<th colspan="2"></th>').text(session.series[j].name));
     }
 
     for (var i = 0; i < benchmarks.length; i++) {
@@ -142,9 +136,8 @@
       // Track which language was the fastest
       var timings = [];
       var bestLanguage = 0;
-      for (var j = 0; j < languages.length; j++) {
-        var language = languages[j];
-        var languageTimings = session[language][benchmark];
+      for (var j = 0; j < session.series.length; j++) {
+        var languageTimings = session.series[j].data[benchmark];
         if (languageTimings) {
           var med = median(languageTimings);
           timings.push(med);
@@ -156,8 +149,7 @@
 
       // Insert the per-language timings into the table
       var bestValue = timings[bestLanguage];
-      for (var j = 0; j < languages.length; j++) {
-        var language = languages[j];
+      for (var j = 0; j < session.series.length; j++) {
         var med = timings[j];
         var valueCell = $('<td></td>').appendTo(tr);
         var multiplierCell = $('<td></td>').appendTo(tr);
@@ -171,15 +163,13 @@
           }
           decorateMultiplierCell(multiplierCell, multiplier);
         }
-        tr.append($('<td></td>'));
       }
     }
 
     // Insert the binary size stats.
     header = $('<tr></tr>').appendTo(table);
     header.append($('<th>Harness size</th>'));
-    for (var j = 0; j < languages.length; j++) {
-      header.append($('<th></th>'));
+    for (var j = 0; j < session.series.length; j++) {
       header.append($('<th></th>'));
       header.append($('<th></th>'));
     }
@@ -192,16 +182,15 @@
 
       var bestLanguage = 0;
       var sizes = [];
-      for (var j = 0; j < languages.length; j++) {
-        var language = languages[j];
-          var size = session[language].harnessSize[harnessSizeKey];
+      for (var j = 0; j < session.series.length; j++) {
+          var size = session.series[j].data.harnessSize[harnessSizeKey];
           sizes.push(size);
           if (size < sizes[bestLanguage]) {
               bestLanguage = j;
           }
       }
 
-      for (var j = 0; j < languages.length; j++) {
+      for (var j = 0; j < session.series.length; j++) {
         var size = sizes[j];
         var multiplier = size / sizes[bestLanguage];
         var formattedSize = size.toLocaleString() + '&nbsp;b';
@@ -213,13 +202,12 @@
             multiplierCell.text('(' + multiplier.toFixed(1) + 'x)');
         }
         decorateMultiplierCell(multiplierCell, multiplier);
-        tr.append($('<td></td>'));
       }
     }
 
     var tfoot = $('<tfoot></tfoot>').appendTo(table);
     var footerRow = $('<tr></tr>').appendTo(tfoot);
-    var colspan = 3 * languages.length + 1;
+    var colspan = 3 * session.series.length + 1;
     var footerCell =
         $('<td colspan="' + colspan + '"></td>').appendTo(footerRow);
       footerCell.text('Green highlights the best result for each test. ' +
@@ -247,12 +235,12 @@
       var title = session.type;
       var header = $('<h3></h3>').addClass('row').text(title);
 
-      var subtitle = 'Branch <tt>' + session.branch +
-          '</tt>, commit <tt>' + session.commit + '</tt>';
+      var subtitle = 'Working tree was at <tt>' + session.branch +
+          '</tt>, commit <tt>' + session.commit.substr(0, 6) + '</tt>';
       if (session.uncommitted_changes) {
         subtitle += ' (with uncommited changes)';
       }
-      subtitle += ', run on ' + formattedDate;
+      subtitle += ' &ndash; ' + formattedDate;
 
       header.append($('<small></small>').html(subtitle));
       $('#container').append('<hr>');
@@ -260,8 +248,8 @@
 
       var id = 'chart' + i;
       var row = $('<div></div>').addClass('row');
-      var chartColumn = $('<div></div>').addClass('col-md-9');
-      var tableColumn = $('<div></div>').addClass('col-md-3');
+      var chartColumn = $('<div></div>').addClass('col-md-8');
+      var tableColumn = $('<div></div>').addClass('col-md-4');
 
       row.append(chartColumn);
       row.append(tableColumn);
@@ -270,12 +258,9 @@
       var chart = $('<div></div>').attr('id', id).addClass('chart');
       chartColumn.append(chart);
 
-      for (var j = 0; j < languages.length; j++) {
-        var language = languages[j];
-        if (session[language]) {
-          var series = createSeries(session, language);
-          allSeries.push(series);
-        }
+      for (var j = 0; j < session.series.length; j++) {
+        var series = createSeries(session, session.series[j]);
+        allSeries.push(series);
       }
 
       Plotly.newPlot(id, allSeries, layout, {

+ 163 - 45
Performance/perf_runner.sh

@@ -41,22 +41,52 @@ readonly GOOGLE_PROTOBUF_CHECKOUT=${GOOGLE_PROTOBUF_CHECKOUT:-"$script_dir/../..
 
 function usage() {
   cat >&2 <<EOF
-Usage: $0 [-p <true|false>] [-s2|-s3] <field count> <field types...>
-
-Currently supported field types:
-    int32, sint32, uint32, fixed32, sfixed32,
-    int64, sint64, uint64, fixed64, sfixed64,
-    float, double, string,
-    ...and repeated variants of the above.
-
-    Additionally, you can specify "all" to run the harness
-    multiple times with all of the (non-repeated) field types
-    listed above.
-
-Options:
-    -p <true|false>: Adds a packed option to each field.
-    -s[23]:          Generate proto2 or proto3 syntax. proto3 is
-                     the default.
+SYNOPSIS
+    $0 [-c <rev|c++> -c <rev|c++> ...] [-p <true|false>] [-s2|-s3] <field count> <field types...>
+
+    Currently supported field types:
+        int32, sint32, uint32, fixed32, sfixed32,
+        int64, sint64, uint64, fixed64, sfixed64,
+        float, double, string,
+        ...and repeated variants of the above.
+
+        Additionally, you can specify "all" to run the harness multiple
+        times with all of the (non-repeated) field types listed above.
+        ("all" is not compatible with revision comparisons.)
+
+OPTIONS
+    -c <rev|c++> [-c <rev|c++> ...]
+        A git revision (a commit hash, branch name, or tag) or the
+        string "c++" that will be performance tested and added as a
+        series in the result plot to compare against the current
+        working tree state. Can be specified multiple times. The current
+        working tree is always tested and included as the final series.
+
+        If no "-c" options are specified, the working tree is compared
+        against the C++ implementation.
+
+    -p <true|false>
+        If true, the generator adds a packed option to each field.
+        Defaults to false.
+
+    -s2, -s3
+        Indicates whether proto2 or proto3 syntax should be generated.
+        The default is proto3.
+
+EXAMPLES
+    $0 100 fixed32
+        Runs the C++ harness and Swift harness in the current working
+        tree using a message with 100 fixed32 fields.
+
+    $0 -c string-speedup -p true 100 "repeated string"
+        Runs the Swift harness in its state at branch "string-speedup"
+        and then again in the current working tree, using a message
+        with 100 packed repeated string fields.
+
+    $0 -c 4d0b78 -c c++ -s2 100 bytes
+        Runs the Swift harness in its state at commit 4d0b78, the C++
+        harness, and then the Swift harness in the current working tree,
+        using a proto2 message with 100 bytes fields.
 EOF
   exit 1
 }
@@ -106,7 +136,9 @@ function run_harness_and_concatenate_results() {
   partial_results="$3"
 
   cat >> "$partial_results" <<EOF
-    "$language": {
+    {
+      name: "$language",
+      data: {
 EOF
 
   echo "Running $language test harness alone..."
@@ -124,21 +156,23 @@ EOF
   echo
 
   cat >> "$partial_results" <<EOF
-      harnessSize: {
-        "Unstripped": $unstripped_size,
-        "Stripped": $stripped_size,
+        harnessSize: {
+          "Unstripped": $unstripped_size,
+          "Stripped": $stripped_size,
+        },
       },
     },
 EOF
 }
 
 function profile_harness() {
-  language="$1"
+  description="$1"
   harness="$2"
+  perf_dir="$3"
 
-  echo "Running $language test harness in Instruments..."
+  echo "Running $description test harness in Instruments..."
   instruments -t "$script_dir/Protobuf" -D "$results_trace" \
-      "$harness" -e DYLD_LIBRARY_PATH "$script_dir/_generated"
+      "$harness" -e DYLD_LIBRARY_PATH "$perf_dir/_generated"
 }
 
 # Inserts the partial visualization results from all the languages tested into
@@ -156,19 +190,38 @@ function insert_visualization_results() {
   mv "${results_js}.new" "$results_js"
 }
 
-# ---------------------------------------------------------------
-# Pull in language specific helpers.
-source "$script_dir/perf_runner_cpp.sh"
-source "$script_dir/perf_runner_swift.sh"
+# build_swift_packages <workdir> <plugin_suffix>
+#
+# Builds the runtime and plug-in with Swift Package Manager, assuming the
+# package manifest and sources are in <workdir>. After being built, the
+# plug-in will be copied and renamed to "protoc-gen-swift<plugin_suffix>"
+# in the same release directory, so that it can be guaranteed unique when
+# running protoc.
+function build_swift_packages() {
+  workdir="$1"
+  plugin_suffix="$2"
+  (
+    echo "Building runtime and plug-in with Swift Package Manager..."
+
+    cd "$workdir" >/dev/null
+    swift build -c release >/dev/null
+    cp .build/release/protoc-gen-swift \
+        ".build/release/protoc-gen-swift${plugin_suffix}"
+  )
+}
 
 # ---------------------------------------------------------------
-# Script main logic.
+# Process command line options.
 
+declare -a comparisons
 packed=""
 proto_syntax="3"
 
-while getopts "p:s:" arg; do
+while getopts "c:p:s:" arg; do
   case "${arg}" in
+    c)
+      comparisons+=("$OPTARG")
+      ;;
     p)
       packed="$OPTARG"
       ;;
@@ -198,13 +251,23 @@ else
   readonly requested_field_types=( "$@" )
 fi
 
-# Make sure the runtime and plug-in are up to date first.
-( cd "$script_dir/.." >/dev/null; swift build -c release )
+# ---------------------------------------------------------------
+# Set up a hook to cleanup revision comparison checkouts when the script
+# completes.
+declare -a CLEANUP_WHEN_DONE
+function cleanup_revision_checkouts() {
+  if [[ "${#CLEANUP_WHEN_DONE[@]}" -ne 0 ]]; then
+    rm -rf "${CLEANUP_WHEN_DONE[@]}"
+  fi
+}
+trap cleanup_revision_checkouts EXIT HUP INT QUIT TERM
 
-# Copy the newly built plugin with a new name so that we can ensure that we're
-# invoking the correct one when we pass it on the command line.
-cp "$script_dir/../.build/release/protoc-gen-swift" \
-    "$script_dir/../.build/release/protoc-gen-swiftForPerf"
+# ---------------------------------------------------------------
+# Pull in language-specific runners.
+source "$script_dir/runners/swift.sh"
+
+# ---------------------------------------------------------------
+# Script main logic.
 
 mkdir -p "$script_dir/_generated"
 mkdir -p "$script_dir/_results"
@@ -217,39 +280,94 @@ if [[ ! -f "$results_js" ]]; then
 fi
 
 # Iterate over the requested field types and run the harnesses.
+# TODO: Get rid of this multi-run; instead, just create a proto like
+# TestAllTypes that tests multiple field types in one message. This is more
+# realistic.
 for field_type in "${requested_field_types[@]}"; do
   gen_message_path="$script_dir/_generated/message.proto"
 
   echo "Generating test proto with $field_count fields of type $field_type..."
   generate_test_proto "$field_count" "$field_type"
 
-  protoc --plugin="$script_dir/../.build/release/protoc-gen-swiftForPerf" \
-      --swiftForPerf_out=FileNaming=DropPath:"$script_dir/_generated" \
-      --cpp_out="$script_dir" \
-      "$gen_message_path"
-
   # Start a session.
   partial_results="$script_dir/_results/partial.js"
+  pretty_head_rev="$(git rev-parse --abbrev-ref HEAD)"
   cat > "$partial_results" <<EOF
   {
     date: "$(date -u +"%FT%T.000Z")",
     type: "$field_count fields of type $field_type",
-    branch: "$(git rev-parse --abbrev-ref HEAD)",
+    branch: "$pretty_head_rev",
     commit: "$(git rev-parse HEAD)",
     uncommitted_changes: $([[ -z $(git status -s) ]] && echo false || echo true),
+    series: [
 EOF
 
-  harness_cpp="$script_dir/_generated/harness_cpp"
-  results_trace="$script_dir/_results/$field_count fields of $field_type (cpp)"
-  run_cpp_harness "$harness_cpp"
+  for comparison in "${comparisons[@]}"; do
+    if [[ "$comparison" == "c++" ]]; then
+      source "$script_dir/runners/cpp.sh"
+
+      echo
+      echo "==== Building/running C++ harness ===================="
+      echo
+
+      protoc --cpp_out="$script_dir" "$gen_message_path"
+
+      harness_cpp="$script_dir/_generated/harness_cpp"
+      results_trace="$script_dir/_results/$field_count fields of $field_type (cpp)"
+      run_cpp_harness "$harness_cpp"
+    else
+      commit_hash="$(git rev-parse $comparison)"
+      commit_results="$script_dir/_results/${commit_hash}_${field_type}_${field_count}.js"
+
+      # Check to see if we have past results from that commit cached. If so, we
+      # don't need to run it again.
+      if [[ ! -f "$commit_results" ]]; then
+        echo
+        echo "==== Building/running Swift harness ($comparison) ===================="
+        echo
+
+        # Check out the commit to a temporary directory and create its _generated
+        # directory. (Results will still go in the working tree.)
+        tmp_checkout="$(mktemp -d -t swiftprotoperf)"
+        CLEANUP_WHEN_DONE+=("$tmp_checkout")
+        git --work-tree="$tmp_checkout" checkout "$comparison" -- .
+        mkdir "$tmp_checkout/Performance/_generated"
+
+        build_swift_packages "$tmp_checkout" "ForRev"
+        protoc --plugin="$tmp_checkout/.build/release/protoc-gen-swiftForRev" \
+            --swiftForRev_out=FileNaming=DropPath:"$tmp_checkout/Performance/_generated" \
+            "$gen_message_path"
+
+        harness_swift="$tmp_checkout/Performance/_generated/harness_swift"
+        results_trace="$script_dir/_results/$field_count fields of $field_type (swift)"
+        run_swift_harness "$tmp_checkout" "$comparison" "$commit_results"
+      else
+        echo
+        echo "==== Found cached results for Swift ($comparison) ===================="
+      fi
+
+      cat "$commit_results" >> "$partial_results"
+    fi
+  done
+
+  echo
+  echo "==== Building/running Swift harness (working tree) ===================="
+  echo
+
+  build_swift_packages "$script_dir/.." "ForWorkTree"
+  protoc --plugin="$script_dir/../.build/release/protoc-gen-swiftForWorkTree" \
+      --swiftForWorkTree_out=FileNaming=DropPath:"$script_dir/_generated" \
+      --cpp_out="$script_dir" \
+      "$gen_message_path"
 
   harness_swift="$script_dir/_generated/harness_swift"
   results_trace="$script_dir/_results/$field_count fields of $field_type (swift)"
   display_results_trace="$results_trace"
-  run_swift_harness "$harness_swift"
+  run_swift_harness "$script_dir/.." "working tree" "$partial_results"
 
   # Close out the session.
   cat >> "$partial_results" <<EOF
+    ],
   },
 EOF
 

+ 50 - 0
Performance/runners/cpp.sh

@@ -0,0 +1,50 @@
+#!/bin/bash
+
+# SwiftProtobuf/Performance/runners/cpp.sh - C++ test harness runner
+#
+# This source file is part of the Swift.org open source project
+#
+# Copyright (c) 2014 - 2016 Apple Inc. and the Swift project authors
+# Licensed under Apache License v2.0 with Runtime Library Exception
+#
+# See http://swift.org/LICENSE.txt for license information
+# See http://swift.org/CONTRIBUTORS.txt for the list of Swift project authors
+#
+# -----------------------------------------------------------------------------
+#
+# Functions for running the C++ harness.
+#
+# -----------------------------------------------------------------------------
+
+function run_cpp_harness() {
+  (
+    harness="$1"
+
+    source "$script_dir/generators/cpp.sh"
+
+    echo "Generating C++ harness source..."
+    gen_harness_path="$script_dir/_generated/Harness+Generated.cc"
+    generate_cpp_harness "$field_count" "$field_type"
+
+    echo "Building C++ test harness..."
+    time ( g++ --std=c++11 -O \
+        -o "$harness" \
+        -I "$script_dir" \
+        -I "$GOOGLE_PROTOBUF_CHECKOUT/src" \
+        -L "$GOOGLE_PROTOBUF_CHECKOUT/src/.libs" \
+        -lprotobuf \
+        "$gen_harness_path" \
+        "$script_dir/Harness.cc" \
+        "$script_dir/_generated/message.pb.cc" \
+        "$script_dir/main.cc" \
+    )
+    echo
+
+    # Make sure the dylib is loadable from the harness if the user hasn't
+    # actually installed them.
+    cp "$GOOGLE_PROTOBUF_CHECKOUT"/src/.libs/libprotobuf.*.dylib \
+        "$script_dir/_generated"
+
+    run_harness_and_concatenate_results "C++" "$harness" "$partial_results"
+  )
+}

+ 69 - 0
Performance/runners/swift.sh

@@ -0,0 +1,69 @@
+#!/bin/bash
+
+# SwiftProtobuf/Performance/runners/swift.sh - Swift test harness runner
+#
+# This source file is part of the Swift.org open source project
+#
+# Copyright (c) 2014 - 2016 Apple Inc. and the Swift project authors
+# Licensed under Apache License v2.0 with Runtime Library Exception
+#
+# See http://swift.org/LICENSE.txt for license information
+# See http://swift.org/CONTRIBUTORS.txt for the list of Swift project authors
+#
+# -----------------------------------------------------------------------------
+#
+# Functions for generating the Swift harness.
+#
+# -----------------------------------------------------------------------------
+
+function run_swift_harness() {
+  # Wrapped in a subshell to prevent state from leaking out (important since
+  # this gets run multiple times during a comparison).
+  (
+    root_dir="$1"
+    description="$2"
+    results_file="$3"
+
+    perf_dir="$root_dir/Performance"
+    harness="$perf_dir/_generated/harness_swift"
+
+    # Load the generator from perf_dir so that we use the comparison revision
+    # instead of the working copy if we're running an older checkout.
+    source "$perf_dir/generators/swift.sh"
+
+    echo "Generating Swift harness source..."
+    gen_harness_path="$perf_dir/_generated/Harness+Generated.swift"
+    generate_swift_harness "$field_count" "$field_type"
+
+    # Build the dynamic library to use in the tests.
+    # TODO: Make the dylib a product again in the package manifest and just use
+    # that.
+    echo "Building SwiftProtobuf dynamic library..."
+    xcrun -sdk macosx swiftc -emit-library -emit-module -O -wmo \
+        -o "$perf_dir/_generated/libSwiftProtobuf.dylib" \
+        "$perf_dir/../Sources/SwiftProtobuf/"*.swift
+
+    echo "Building Swift test harness..."
+    time ( xcrun -sdk macosx swiftc -O \
+        -o "$harness" \
+        -I "$perf_dir/_generated" \
+        -L "$perf_dir/_generated" \
+        -lSwiftProtobuf \
+        "$gen_harness_path" \
+        "$perf_dir/Harness.swift" \
+        "$perf_dir/_generated/message.pb.swift" \
+        "$perf_dir/main.swift"
+    )
+    echo
+
+    dylib="$perf_dir/_generated/libSwiftProtobuf.dylib"
+    echo "Swift dylib size before stripping: $(stat -f "%z" "$dylib") bytes"
+    cp "$dylib" "${dylib}_stripped"
+    strip -u -r "${dylib}_stripped"
+    echo "Swift dylib size after stripping:  $(stat -f "%z" "${dylib}_stripped") bytes"
+    echo
+
+    run_harness_and_concatenate_results "Swift ($description)" "$harness" "$results_file"
+    profile_harness "Swift ($description)" "$harness" "$perf_dir"
+  )
+}