Permalink
Browse files

Add Tommi Reiman's benchmarks, clean up comments

  • Loading branch information...
seancorfield committed Oct 20, 2017
1 parent 4a62e28 commit 99a3d6611f973ff47597f4c7df3c36237ab4bde3
Showing with 235 additions and 18 deletions.
  1. +1 −1 CHANGES.md
  2. +8 −3 project.clj
  3. +0 −14 src/main/clojure/clojure/java/jdbc.clj
  4. +226 −0 src/perf/clojure/clojure/java/perf_jdbc.clj
View
@@ -2,7 +2,7 @@ Changes coming in 0.7.4
* Improved discoverability of other `java.jdbc` documentation [JDBC-160](https://dev.clojure.org/jira/browse/JDBC-160).
* Optional specs updated with `:keywordize?` and `:connection-uri` changes from 0.7.2 and 0.7.3 releases.
* Performance improvements to `query` and `reducible-query`.
* Performance improvements, primarily in `query` and `reducible-query`.
* Experimental `:raw?` result set handling in `reducible-query`.
Changes in 0.7.3
View
@@ -33,10 +33,15 @@
:profiles {:1.7 {:dependencies [[org.clojure/clojure "1.7.0"]]}
:1.8 {:dependencies [[org.clojure/clojure "1.8.0"]]}
:1.9 {:dependencies [[org.clojure/clojure "1.9.0-master-SNAPSHOT"]]}
:dev {:dependencies [[org.clojure/test.check "0.9.0"]]}}
:dev {:dependencies [[org.clojure/test.check "0.9.0"]
[criterium "0.4.4"]]}
:perf {:test-paths ["src/perf/clojure"]
:jvm-opts ^:replace ["-server"
"-Xmx4096m"
"-Dclojure.compiler.direct-linking=true"]}}
:repositories {"sonatype-oss-public" "https://oss.sonatype.org/content/groups/public/"
"ws-archiva" "https://d259tvauhnips9.cloudfront.net/archiva/repository/internal/"}
;; include dev profile with 1.9 to pull in test.check
:aliases {"test-all" ["with-profile" "test,1.7:test,1.8:dev,test,1.9" "test"]}
:aliases {"test-all" ["with-profile" "test,1.7:test,1.8:dev,test,1.9" "test"]
"perf" ["with-profile" "default,dev,perf"]}
:min-lein-version "2.0.0")
@@ -1085,20 +1085,6 @@ http://clojure-doc.org/articles/ecosystem/java_jdbc/home.html"}
(result-set-seq rset opts)))))
opts))))
;; performance notes -- goal is to lift as much logic as possible into a "once"
;; pass (so reducible-query preloads all the stuff that doesn't depend on the
;; query results, and then you can repeatedly reduce it, which runs the query
;; each time and runs the minimal result set reduction)
;; done: turn make-cols-unique into a transducer and optimize it
;; done: turn make-keys into a transducer pipeline and lift it
;; done: lift identifier-fn out as make-identifier-fn and refactor
;; done: lift init-reduce
;; done: refactor reducible-result-set to lift identifier-fn out
;; done: call new reducible-result-set version from reducible-query (after calling
;; make-identifier-fn etc)
;; create an optimized version of db-query-with-resultset without :as-arrays?
;; and with options handling lifted
(defn- get-rs-columns
"Given a set of indices, a result set's metadata, and a function to convert
SQL entity names to Clojure column names,
@@ -0,0 +1,226 @@
;; Copyright (c) 2017 Tommmi Reiman, Sean Corfield. All rights reserved.
;; The use and distribution terms for this software are covered by
;; the Eclipse Public License 1.0
;; (http://opensource.org/licenses/eclipse-1.0.php) which can be
;; found in the file epl-v10.html at the root of this distribution.
;; By using this software in any fashion, you are agreeing to be
;; bound by the terms of this license. You must not remove this
;; notice, or any other, from this software.
(ns clojure.java.perf-jdbc
"Performance tests for parts of clojure.java.jdbc.
Start a REPL with `lein perf repl`
Require this namespace and then run `(calibrate)`
followed by `(test-dummy)` and `(test-h2)`
These test compare the raw performance (against an in-memory H2 database)
for hand-crafted Java JDBC calls and various `query` and `reducible-query`
calls."
(:require [criterium.core :as cc]
[clojure.java.jdbc :as sql])
(:import (java.sql Connection PreparedStatement ResultSet Statement ResultSetMetaData)))
(defn calibrate []
;; 840ms
(cc/quick-bench (reduce + (take 10e6 (range)))))
(def db
"Note: loading this namespace creates a connection to the H2 database!"
{:connection (sql/get-connection "jdbc:h2:mem:test_mem")})
(defn create-table! [db]
(sql/db-do-commands
db (sql/create-table-ddl
:fruit
[[:id :int "DEFAULT 0"]
[:name "VARCHAR(32)" "PRIMARY KEY"]
[:appearance "VARCHAR(32)"]
[:cost :int]
[:grade :real]]
{:table-spec ""})))
(defn- drop-table! [db]
(doseq [table [:fruit :fruit2 :veggies :veggies2]]
(try
(sql/db-do-commands db (sql/drop-table-ddl table))
(catch java.sql.SQLException _))))
(defn add-stuff! [db]
(sql/insert-multi! db
:fruit
nil
[[1 "Apple" "red" 59 87]
[2 "Banana" "yellow" 29 92.2]
[3 "Peach" "fuzzy" 139 90.0]
[4 "Orange" "juicy" 89 88.6]]))
(def dummy-con
(reify
Connection
(createStatement [_]
(reify
Statement
(addBatch [_ _])))
(prepareStatement [_ _]
(reify
PreparedStatement
(setObject [_ _ _])
(setString [_ _ _])
(close [_])
(executeQuery [_]
(reify
ResultSet
(getMetaData [_]
(reify
ResultSetMetaData
(getColumnCount [_] 1)
(getColumnLabel [_ _] "name")))
(next [_] true)
(close [_])
(^Object getObject [_ ^int s]
"Apple")
(^Object getObject [_ ^String s]
"Apple")
(^String getString [_ ^String s]
"Apple")))))))
(defn select [db]
(sql/query db ["SELECT * FROM fruit WHERE appearance = ?" "red"]
{:row-fn :name :result-set-fn first}))
(defn select-p [db ps]
(sql/query db [ps "red"]
{:row-fn :name :result-set-fn first}))
(defn select* [^Connection con]
(let [ps (doto (.prepareStatement con "SELECT * FROM fruit WHERE appearance = ?")
(.setObject 1 "red"))
rs (.executeQuery ps)
_ (.next rs)
value (.getObject rs "name")]
(.close ps)
value))
(defn test-dummy []
(do
(let [db {:connection dummy-con}]
(assert (= "Apple" (select db)))
;(time (dotimes [_ 100000] (select db)))
; 3.029268 ms (3030 ns)
(cc/quick-bench (dotimes [_ 1000] (select db))))
(let [con dummy-con]
(assert (= "Apple" (select* con)))
;(time (dotimes [_ 100000] (select* con)))
; 716.661522 ns (0.7ns) -> 4300x faster
(cc/quick-bench (dotimes [_ 1000] (select* con))))))
(defn test-h2 []
(do
(drop-table! db)
(create-table! db)
(add-stuff! db)
(println "Basic select...")
(let [db db]
(assert (= "Apple" (select db)))
(cc/quick-bench (select db)))
(println "Select with prepared statement...")
(let [con (:connection db)]
(with-open [ps (sql/prepare-statement con "SELECT * FROM fruit WHERE appearance = ?")]
(assert (= "Apple" (select-p db ps)))
(cc/quick-bench (select db))))
(println "Reducible query...")
(let [db db
rq (sql/reducible-query db ["SELECT * FROM fruit WHERE appearance = ?" "red"])]
(assert (= "Apple" (reduce (fn [_ row] (reduced (:name row)))
nil rq)))
(cc/quick-bench (reduce (fn [_ row] (reduced (:name row)))
nil rq)))
(println "Reducible query with prepared statement...")
(let [con (:connection db)]
(with-open [ps (sql/prepare-statement con "SELECT * FROM fruit WHERE appearance = ?")]
(let [rq (sql/reducible-query db [ps "red"])]
(assert (= "Apple" (reduce (fn [_ row] (reduced (:name row)))
nil rq)))
(cc/quick-bench (reduce (fn [_ row] (reduced (:name row)))
nil rq)))))
(println "Reducible query with prepared statement and simple identifiers...")
(let [con (:connection db)]
(with-open [ps (sql/prepare-statement con "SELECT * FROM fruit WHERE appearance = ?")]
(let [rq (sql/reducible-query db [ps "red"]
{:keywordize? false :identifiers identity})]
(assert (= "Apple" (reduce (fn [_ row] (reduced (get row "NAME")))
nil rq)))
(cc/quick-bench (reduce (fn [_ row] (reduced (get row "NAME")))
nil rq)))))
(println "Reducible query with prepared statement and raw result set...")
(let [con (:connection db)]
(with-open [ps (sql/prepare-statement con "SELECT * FROM fruit WHERE appearance = ?")]
(let [rq (sql/reducible-query db [ps "red"] {:raw? true})]
(assert (= "Apple" (reduce (fn [_ row] (reduced (:name row)))
nil rq)))
(cc/quick-bench (reduce (fn [_ row] (reduced (:name row)))
nil rq)))))
(println "Reducible query with raw result set...")
(let [db db
rq (sql/reducible-query
db
["SELECT * FROM fruit WHERE appearance = ?" "red"]
{:raw? true})]
(assert (= "Apple" (reduce (fn [_ row] (reduced (:name row)))
nil rq)))
(cc/quick-bench (reduce (fn [_ row] (reduced (:name row)))
nil rq)))
(println "Repeated reducible query with raw result set...")
(let [db db]
(assert (= "Apple" (reduce (fn [_ row] (reduced (:name row)))
nil (sql/reducible-query
db
["SELECT * FROM fruit WHERE appearance = ?" "red"]
{:raw? true}))))
(cc/quick-bench (reduce (fn [_ row] (reduced (:name row)))
nil (sql/reducible-query
db
["SELECT * FROM fruit WHERE appearance = ?" "red"]
{:raw? true}))))
(println "Raw Java...")
(let [con (:connection db)]
(assert (= "Apple" (select* con)))
(cc/quick-bench (select* con)))))
(comment
(calibrate)
(test-dummy)
(test-h2)
;; The following are just some things I was double-checking while adding
;; to Tommi's original tests -- Sean.
;; Shows the row is a reify instance, but you can select by key:
(reduce (fn [_ row] (println row) (reduced (:name row)))
nil (sql/reducible-query
db
["SELECT * FROM fruit WHERE appearance = ?" "red"]
{:raw? true}))
;; Shows you can construct a map result using select-keys:
(reduce (fn [_ row] (reduced (select-keys row [:cost])))
nil (sql/reducible-query
db
["SELECT * FROM fruit WHERE appearance = ?" "red"]
{:raw? true}))
;; Shows you can reconstruct an entire result set (with very little overhead):
(transduce (map #(select-keys % [:id :name :cost :appearance :grade]))
conj [] (sql/reducible-query
db
"SELECT * FROM fruit"
{:raw? true})))

0 comments on commit 99a3d66

Please sign in to comment.