perf_runner.sh 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373
  1. #!/bin/bash
  2. # SwiftProtobuf/Performance/perf_runner.swift - Performance test runner
  3. #
  4. # This source file is part of the Swift.org open source project
  5. #
  6. # Copyright (c) 2014 - 2016 Apple Inc. and the Swift project authors
  7. # Licensed under Apache License v2.0 with Runtime Library Exception
  8. #
  9. # See http://swift.org/LICENSE.txt for license information
  10. # See http://swift.org/CONTRIBUTORS.txt for the list of Swift project authors
  11. #
  12. # -----------------------------------------------------------------------------
  13. #
  14. # This script creates a .proto file with the requested number of fields of a
  15. # particular type and generates a test harness that populates it, serializes
  16. # it, and then parses it back out. Then it generates the Swift source for the
  17. # .proto file, builds the harness, and runs it under Instruments to collect
  18. # timing samples.
  19. #
  20. # The results are stored in the Performance/Results directory. The instruments
  21. # traces are named based on the number and type of fields; multiple runs with
  22. # the same inputs will append to the existing trace file if it is present.
  23. #
  24. # In addition to the collected samples, this script also prints the time it
  25. # takes to compile/link the harness and the byte size of the harness before and
  26. # after stripping.
  27. #
  28. # -----------------------------------------------------------------------------
  29. set -eu
  30. cd "$(dirname $0)"
  31. # The 'swift' command is in the same directory as 'swiftc',
  32. # 'swiftc' is sometimes found via the common SWIFT_EXEC var:
  33. if [ -n "${SWIFT_EXEC:-}" ]; then
  34. SWIFT=`dirname ${SWIFT_EXEC}`/swift
  35. else
  36. SWIFT="swift"
  37. fi
  38. # Directory containing this script
  39. readonly script_dir=`pwd`
  40. # Change this if your checkout of github.com/protocolbuffers/protobuf is in a
  41. # different location.
  42. readonly GOOGLE_PROTOBUF_CHECKOUT=${GOOGLE_PROTOBUF_CHECKOUT:-"$script_dir/../../protobuf"}
  43. readonly PROTOC=${PROTOC:-"${GOOGLE_PROTOBUF_CHECKOUT}/protoc"}
  44. function usage() {
  45. cat >&2 <<EOF
  46. SYNOPSIS
  47. $0 [-c <rev|c++> -c <rev|c++> ...] [-p <true|false>] [-s2|-s3] [<field count> <field type>]
  48. Currently supported field types:
  49. int32, sint32, uint32, fixed32, sfixed32,
  50. int64, sint64, uint64, fixed64, sfixed64,
  51. float, double, string,
  52. ...and repeated variants of the above.
  53. If you omit the field count and type, the harness will run with
  54. a message that contains multiple field types -- both singular and
  55. repeated -- and nested messages.
  56. OPTIONS
  57. -c <rev|c++> [-c <rev|c++> ...]
  58. A git revision (a commit hash, branch name, or tag) or the
  59. string "c++" that will be performance tested and added as a
  60. series in the result plot to compare against the current
  61. working tree state. Can be specified multiple times. The current
  62. working tree is always tested and included as the final series.
  63. If no "-c" options are specified, the working tree is compared
  64. against the C++ implementation.
  65. -p <true|false>
  66. If true, the generator adds a packed option to each field.
  67. Defaults to false.
  68. -s2, -s3
  69. Indicates whether proto2 or proto3 syntax should be generated.
  70. The default is proto3.
  71. EXAMPLES
  72. $0 100 fixed32
  73. Runs the C++ harness and Swift harness in the current working
  74. tree using a message with 100 fixed32 fields.
  75. $0 -c string-speedup -p true 100 "repeated string"
  76. Runs the Swift harness in its state at branch "string-speedup"
  77. and then again in the current working tree, using a message
  78. with 100 packed repeated string fields.
  79. $0 -c 4d0b78 -c c++ -s2 100 bytes
  80. Runs the Swift harness in its state at commit 4d0b78, the C++
  81. harness, and then the Swift harness in the current working tree,
  82. using a proto2 message with 100 bytes fields.
  83. $0 -c 4d0b78
  84. Runs the Swift harness in its state at commit 4d0b78 and then
  85. the Swift harness in the current working tree, using a proto3
  86. message with multiple field types.
  87. EOF
  88. exit 1
  89. }
  90. # ---------------------------------------------------------------
  91. # Functions for running harnesses and collecting results.
  92. # Executes the test harness for a language under Instruments and concatenates
  93. # its results to the partial results file.
  94. function run_harness_and_concatenate_results() {
  95. language="$1"
  96. harness="$2"
  97. partial_results="$3"
  98. cat >> "$partial_results" <<EOF
  99. {
  100. name: "$language",
  101. data: {
  102. EOF
  103. echo "Running $language test harness alone..."
  104. sleep 3
  105. DYLD_LIBRARY_PATH=`dirname $harness` "$harness" "$partial_results"
  106. sleep 3
  107. cp "$harness" "${harness}_stripped"
  108. strip -u -r "${harness}_stripped"
  109. unstripped_size=$(stat -f "%z" "$harness")
  110. stripped_size=$(stat -f "%z" "${harness}_stripped")
  111. echo "${language} harness size before stripping: $unstripped_size bytes"
  112. echo "${language} harness size after stripping: $stripped_size bytes"
  113. echo
  114. cat >> "$partial_results" <<EOF
  115. harnessSize: {
  116. "Unstripped": $unstripped_size,
  117. "Stripped": $stripped_size,
  118. },
  119. },
  120. },
  121. EOF
  122. }
  123. function profile_harness() {
  124. description="$1"
  125. harness="$2"
  126. perf_dir="$3"
  127. echo "Running $description test harness in Instruments..."
  128. mkdir -p "$results_trace"
  129. xctrace record --template 'Time Profiler' --output "$results_trace" \
  130. --env DYLD_LIBRARY_PATH="$perf_dir/_generated" \
  131. --launch -- "$harness"
  132. }
  133. # Inserts the partial visualization results from all the languages tested into
  134. # the final results.js file.
  135. function insert_visualization_results() {
  136. while IFS= read -r line
  137. do
  138. if [[ "$line" =~ ^//NEW-DATA-HERE$ ]]; then
  139. cat "$partial_results"
  140. fi
  141. echo "$line"
  142. done < "$results_js" > "${results_js}.new"
  143. rm "$results_js"
  144. mv "${results_js}.new" "$results_js"
  145. }
  146. # build_swift_packages <workdir> <plugin_suffix>
  147. #
  148. # Builds the runtime and plug-in with Swift Package Manager, assuming the
  149. # package manifest and sources are in <workdir>. After being built, the
  150. # plug-in will be copied and renamed to "protoc-gen-swift<plugin_suffix>"
  151. # in the same release directory, so that it can be guaranteed unique when
  152. # running protoc.
  153. function build_swift_packages() {
  154. workdir="$1"
  155. plugin_suffix="$2"
  156. (
  157. echo "Building runtime and plug-in with Swift Package Manager..."
  158. cd "$workdir" >/dev/null
  159. "${SWIFT}" build -c release >/dev/null
  160. cp .build/release/protoc-gen-swift \
  161. ".build/release/protoc-gen-swift${plugin_suffix}"
  162. )
  163. }
  164. # ---------------------------------------------------------------
  165. # Process command line options.
  166. declare -a comparisons
  167. packed=""
  168. proto_syntax="3"
  169. while getopts "c:p:s:" arg; do
  170. case "${arg}" in
  171. c)
  172. comparisons+=("$OPTARG")
  173. ;;
  174. p)
  175. packed="$OPTARG"
  176. ;;
  177. s)
  178. proto_syntax="$OPTARG"
  179. ;;
  180. esac
  181. done
  182. shift "$((OPTIND-1))"
  183. if [[ "$proto_syntax" != "2" ]] && [[ "$proto_syntax" != "3" ]]; then
  184. usage
  185. fi
  186. if [[ "$#" -eq 0 ]]; then
  187. readonly proto_type=heterogeneous
  188. readonly run_count=100
  189. elif [[ "$#" -eq 2 ]]; then
  190. readonly proto_type=homogeneous
  191. readonly run_count=1000
  192. readonly field_count="$1"
  193. readonly field_type="$2"
  194. else
  195. usage
  196. fi
  197. if [[ "${#comparisons[@]}" -eq 0 ]]; then
  198. comparisons+=("c++")
  199. fi
  200. # ---------------------------------------------------------------
  201. # Set up a hook to cleanup revision comparison checkouts when the script
  202. # completes.
  203. declare -a CLEANUP_WHEN_DONE
  204. GIT_WORKTREE=""
  205. function cleanup_revision_checkouts() {
  206. if [[ "${#CLEANUP_WHEN_DONE[@]}" -ne 0 ]]; then
  207. rm -rf "${CLEANUP_WHEN_DONE[@]}"
  208. fi
  209. if [ "$GIT_WORKTREE" != "" ]; then
  210. git worktree remove "$GIT_WORKTREE"
  211. fi
  212. }
  213. trap cleanup_revision_checkouts EXIT HUP INT QUIT TERM
  214. # ---------------------------------------------------------------
  215. # Pull in language-specific helpers.
  216. source "$script_dir/generators/proto.sh"
  217. source "$script_dir/runners/swift.sh"
  218. # ---------------------------------------------------------------
  219. # Script main logic.
  220. mkdir -p "$script_dir/_generated"
  221. mkdir -p "$script_dir/_results"
  222. # If the visualization results file isn't there, copy it from the template so
  223. # that the harnesses can populate it.
  224. results_js="$script_dir/_results/results.js"
  225. if [[ ! -f "$results_js" ]]; then
  226. cp "$script_dir/js/results.js.template" "$results_js"
  227. fi
  228. # Generate the harness.
  229. gen_message_path="$script_dir/_generated/message.proto"
  230. if [[ "$proto_type" == "homogeneous" ]]; then
  231. echo "Generating test proto with $field_count fields of type $field_type..."
  232. generate_homogeneous_test_proto "$gen_message_path"
  233. report_type="$field_count fields of type $field_type"
  234. commit_results_suffix="${field_type}_${field_count}"
  235. else
  236. echo "Generating test proto with various fields..."
  237. generate_heterogeneous_test_proto "$gen_message_path"
  238. report_type="various fields"
  239. commit_results_suffix="heterogeneous"
  240. fi
  241. # Start a session.
  242. partial_results="$script_dir/_results/partial.js"
  243. pretty_head_rev="$(git rev-parse --abbrev-ref HEAD)"
  244. cat > "$partial_results" <<EOF
  245. {
  246. date: "$(date -u +"%FT%T.000Z")",
  247. type: "$report_type",
  248. branch: "$pretty_head_rev",
  249. commit: "$(git rev-parse HEAD)",
  250. uncommitted_changes: $([[ -z $(git status -s) ]] && echo false || echo true),
  251. series: [
  252. EOF
  253. for comparison in "${comparisons[@]}"; do
  254. if [[ "$comparison" == "c++" ]]; then
  255. source "$script_dir/runners/cpp.sh"
  256. echo
  257. echo "==== Building/running C++ harness ===================="
  258. echo
  259. ${PROTOC} --cpp_out="$script_dir/_generated" \
  260. --proto_path=`dirname $gen_message_path` \
  261. "$gen_message_path"
  262. harness_cpp="$script_dir/_generated/harness_cpp"
  263. run_cpp_harness "$harness_cpp"
  264. else
  265. commit_hash="$(git rev-parse $comparison)"
  266. commit_results="$script_dir/_results/${commit_hash}_${commit_results_suffix}.js"
  267. # Check to see if we have past results from that commit cached. If so, we
  268. # don't need to run it again.
  269. if [[ ! -f "$commit_results" ]]; then
  270. echo
  271. echo "==== Building/running Swift harness ($comparison) ===================="
  272. echo
  273. # Check out the commit to a temporary directory and create its _generated
  274. # directory. (Results will still go in the working tree.)
  275. GIT_WORKTREE="$(mktemp -d `pwd`/_generated/swiftprotoperf.XXXXXX)"
  276. CLEANUP_WHEN_DONE+=("$GIT_WORKTREE")
  277. git worktree add "$GIT_WORKTREE" "$comparison"
  278. mkdir "$GIT_WORKTREE/Performance/_generated"
  279. build_swift_packages "$GIT_WORKTREE" "ForRev"
  280. ${PROTOC} --plugin="$GIT_WORKTREE/.build/release/protoc-gen-swiftForRev" \
  281. --swiftForRev_out=FileNaming=DropPath:"$GIT_WORKTREE/Performance/_generated" \
  282. --proto_path=`dirname $gen_message_path` \
  283. "$gen_message_path"
  284. harness_swift="$GIT_WORKTREE/Performance/_generated/harness_swift"
  285. results_trace="$script_dir/_results/$report_type (swift)"
  286. run_swift_harness "$GIT_WORKTREE" "$comparison" "$commit_results"
  287. else
  288. echo
  289. echo "==== Found cached results for Swift ($comparison) ===================="
  290. fi
  291. cat "$commit_results" >> "$partial_results"
  292. fi
  293. done
  294. echo
  295. echo "==== Building/running Swift harness (working tree) ===================="
  296. echo
  297. build_swift_packages "$script_dir/.." "ForWorkTree"
  298. ${PROTOC} --plugin="$script_dir/../.build/release/protoc-gen-swift" \
  299. --swift_out=FileNaming=DropPath:`dirname $gen_message_path` \
  300. --proto_path=`dirname $gen_message_path` \
  301. "$gen_message_path"
  302. harness_swift="$script_dir/_generated/harness_swift"
  303. results_trace="$script_dir/_results/$report_type (swift)"
  304. display_results_trace="$results_trace"
  305. run_swift_harness "$script_dir/.." "working tree" "$partial_results"
  306. # Close out the session.
  307. cat >> "$partial_results" <<EOF
  308. ],
  309. },
  310. EOF
  311. insert_visualization_results "$partial_results" "$results_js"
  312. # Open the HTML report at the end.
  313. open -g "$script_dir/harness-visualization.html"