-
Notifications
You must be signed in to change notification settings - Fork 46
Slow compilation/load #32
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Comments
while direct import of Instant and Duration is
|
Yep, this is definitely not nice. I haven't profiled the loading stage though. Maybe I will get to it. Hopefully someone will do it ahead of me :) |
I've taken a look at the flame graph of a single
To profile over multiple runs we'd need to unload the compiled code between runs (e.g. running in isolated shims using Shimdandy) or merge flame graphs from different runs afterwards (haven't looked at how this would be done). There's some time spent on collecting static fields from some of the classes in the
Otherwise nothing stood out in particular. Bear in mind I've only spent like 10 minutes on setting this up, so a more thorough look is needed. |
Hello there, Looking at the source of Thankfully, there is an easy way out of this - simply hard-code the fields one-by-one. I know it's tedious, but it's pretty obvious that this will help bring times down. I can have a go at a PR later in January if you're interested. Hope that helps...:) |
Here is an (probably incomplete) example of what I mean: (ns java-time.fields
(:import (java.time.temporal ChronoField IsoFields Temporal TemporalField)
(java.time OffsetDateTime LocalDate LocalDateTime ZonedDateTime LocalTime YearMonth)))
(defn- getter-for
([f]
(getter-for f false))
([^TemporalField f long?]
[f (if long?
#(.getLong ^Temporal % f)
#(.get ^Temporal % f))]))
(def chrono-fields
{:era (getter-for ChronoField/ERA)
:year (getter-for ChronoField/YEAR)
:year-of-era (getter-for ChronoField/YEAR_OF_ERA)
:proleptic-month (getter-for ChronoField/PROLEPTIC_MONTH)
:day-of-year (getter-for ChronoField/DAY_OF_YEAR)
:day-of-month (getter-for ChronoField/DAY_OF_MONTH)
:day-of-week (getter-for ChronoField/DAY_OF_WEEK)
:month-of-year (getter-for ChronoField/MONTH_OF_YEAR)
:hour-of-day (getter-for ChronoField/HOUR_OF_DAY)
:hour-of-ampm (getter-for ChronoField/HOUR_OF_AMPM)
:second-of-day (getter-for ChronoField/SECOND_OF_DAY)
:second-of-minute (getter-for ChronoField/SECOND_OF_MINUTE)
:milli-of-day (getter-for ChronoField/MILLI_OF_DAY)
:milli-of-second (getter-for ChronoField/MILLI_OF_SECOND)
:micro-of-day (getter-for ChronoField/MICRO_OF_DAY true)
:micro-of-second (getter-for ChronoField/MICRO_OF_SECOND)
:nano-of-day (getter-for ChronoField/NANO_OF_DAY true)
:nano-of-second (getter-for ChronoField/NANO_OF_SECOND true)
:offset-seconds (getter-for ChronoField/OFFSET_SECONDS)
})
(def ISO-fields
{:week-based-year (getter-for IsoFields/WEEK_BASED_YEAR)
:quarter-of-year (getter-for IsoFields/QUARTER_OF_YEAR)
:day-of-quarter (getter-for IsoFields/DAY_OF_QUARTER)
:week-of-year (getter-for IsoFields/WEEK_OF_WEEK_BASED_YEAR)
})
;;INDIVIDUAL FIELD ACCESS
(def year-month-fields
;; not all ISO/CHRONO fields can be extracted from a YearMonth
(merge
(select-keys chrono-fields [:year :month-of-year :year-of-era :proleptic-month])
(select-keys ISO-fields [:quarter-of-year])))
(def local-time-fields
;; not all CHRONO fields can be extracted from a LocalTime
(select-keys chrono-fields
[:hour-of-day
:hour-of-ampm
:second-of-day
:second-of-minute
:milli-of-day
:milli-of-second
:micro-of-day
:micro-of-second
:nano-of-day
:nano-of-second]))
(def local-date-fields
(merge ISO-fields
;; not all CHRONO fields can be extracted from a LocalDate
(select-keys chrono-fields
[:day-of-year
:day-of-month
:day-of-week
:month-of-year
:year-of-era
:year
:proleptic-month])))
(def local-datetime-fields
(merge ISO-fields chrono-fields))
(def offset-datetime-fields
(merge local-datetime-fields
(select-keys chrono-fields [:offset-seconds])))
(defprotocol FieldAccess
(get-field [this field]))
(extend-protocol FieldAccess
YearMonth
(get-field [this k]
(when-let [[_ get*] (year-month-fields k)]
(get* this)))
LocalTime
(get-field [this k]
(when-let [[_ get*] (local-time-fields k)]
(get* this)))
LocalDate
(get-field [this k]
(when-let [[_ get*] (local-date-fields k)]
(get* this)))
LocalDateTime
(get-field [this k]
(when-let [[_ get*] (local-datetime-fields k)]
(get* this)))
ZonedDateTime
(get-field [this k]
(when-let [[_ get*] (offset-datetime-fields k)] ;; reuse offset fields
(get* this)))
OffsetDateTime
(get-field [this k]
(when-let [[_ get*] (offset-datetime-fields k)]
(get* this)))
)
(defn field-from
"Returns the value (typically an int) for the
specified field <k>."
[x k]
(get-field x k))
(defn fields-from
"Returns the values (typically an integer)
for the specified fields <ks>."
[x & ks]
(map (partial field-from x) ks))
(defn lookup-field
"Looks up <k> as an ISO/Chrono field."
[k]
(or (chrono-fields k)
(ISO-fields k))) |
This looks interesting. However, according to the list of calls to Could you please clarify if my understanding is correct? Would also be great if you could run a start-up benchmark/flame-graph analysis pre/post changes and confirm the results. |
Hi there, Yes you're right - the above code is rather intrusive. Have a look at this commit though. It completely removes all usages of |
And this commit removes usages of get-static-fields-of-type from format.clj. The only file that still uses it is |
Great! Would you submit the PR with this change? |
@jimpil should I try to create a patch from your commits myself or would you want to submit a PR here? |
Let's merge the |
@jimpil do you want to create a separate PR now given the Datafiable is off the table? |
Yeah let's close/decline the other PR, and I'll open a new one |
Hi, was there any progress on this?
While the direct import is very fast:
Adding a 1.5 sec on each run/test cycle is a large tax |
I'm really sorry guys, I have to admit I completely forgot about this, and I'm afraid I'm not in a position to pick it up at the moment... @dm3 Feel free to (re)use the commits linked in previous comments in order to get rid of all reflective operations in a future version. As mentioned in a previous comment, the idea is simple - just enumerate the possibilities rather than trying to find them (via reflection) at compile-time. Hope that helps, and apologies once again for letting this hang for so long... |
@smessems did you try with the latest master? What's the result there? |
@dm3 I have the latest - 0.3.2 |
@smessems 0.3.2 is the latest release. There's a commit in the master that should lower the time somewhat. If you want to check, you'd have to clone the repo and use the code in the master branch instead of 0.3.2 release. |
@dm3 Running with |
Add fn to preload impl and pointers to AOT compilation if this still isn't good enough.
saves 200ms on my machine before: user=> (time ((requiring-resolve 'java-time/instant))) "Elapsed time: 1363.621694 msecs" after: user=> (time ((requiring-resolve 'java-time/instant))) "Elapsed time: 1113.720916 msecs"
The implementation is now loaded asynchronously, with about 200ms serialized overhead on my machine. Perhaps in real-world conditions (where you're waiting for other files to compile) this might eliminate a lot of the overhead. |
A few more optimizations gives about 400ms speedup for serialized load on my machine: $ git checkout 0.3.3
$ lein repl
user=> (time ((requiring-resolve 'java-time/instant)))
"Elapsed time: 1435.617784 msecs"
$ git checkout master
$ lein repl
user=> (time ((requiring-resolve 'java-time/instant)))
"Elapsed time: 1028.810277 msecs" |
As of b758f72 (compared to 0.3.3):
|
Reverted the approach: 13256ef |
clojure.java-time
takes seconds to load:Recent mBP, macOS 10.14, Clojure 1.8 or 1.10:
Slightly older mBP, macOS 10.14, Clojure 1.10:
Haven't tested on other OSes.
The text was updated successfully, but these errors were encountered: