3rd issue: metadata and function equality: I've never really understood the motivation for "Two objects that differ only in metadata are equal." Is there a good reference for that?
For my purposes, it would probably be better if 'metadata' did affect equality. Perhaps I shouldn't be overloading the metadata mechanism after all? On Wed, Sep 20, 2017 at 11:57 AM, John McDonald <palisades.la...@gmail.com> wrote: > 2nd issue: Benchmarks > > I use both criterium and simple 'run repeatedly and divide the clock time'. > > I've had trouble getting consistent results from run to run with either. > > Most recently (yesterday) I've added many more warmup runs, giving HotSpot > lots of time to do its stuff, which seems to be stabilizing the results. > This might be unfair, because in practice a lot of important code might > never run that many times. > At least, it gives the limiting performance of code that runs a lot. > > Unfortunately, this benchmark is split over 3 github projects, so it might > be a little hard to follow. > > The task I'm timing is computing the sum of squares of the elements in a > moderately large (4m elements) double[] array (aka L2Norm). > > I've compared 8 implementations, all of which accumulate the sum in Java. > > 6.37ms inline --- naive sum of squares in Java. > 6.37ms invokestatic --- call a static method in Java to square the > elements. > 6.38ms primitive --- calls square.invokePrim(x[i]) to square the elements > 6.37ms boxprimitive --- calls square.invoke(x[i]) to square the elements > 6.37ms funxprimitive --- calls funxSquare.invokePrim(), where funxSquare > is square wrapped with MetaFn, an experiment metadata wrapper. > 6.38ms funxboxed --- calls funxSquare.invoke() > 43.55ms boxed --- calls boxedSquare.invoke() , where boxedSquare is a > version of square without type hints. > 151.61ms cljmeta --- calls metaSquare.invoke(), where metasquare is the > result of (with-meta square {...}) > > Clojure 1.8.0, Oracle JDK 1.8, Win10, Lenovo X1 i5-7300U. > Sum of squares for each of 2 double[4194304], in 2 threads, concurrently. > > boxed and cljmeta create a lot of garbage, causing their clock times to be > more variable, depending on exactly what GC does.. > It's possible they appear relatively faster with a small number of total > calls, if they don't trigger a GC, and HotSpot doesn't fully optimize the > others. > I think this is a valid usage pattern, but many many calls to small > functions is the case that I'm interested in at present. > > > Main scripts: > > Using criterium: > https://github.com/palisades-lakes/function-experiments/ > blob/dynesty/src/scripts/clojure/palisades/lakes/funx/l2norm/bench.clj > > Running the benchmark 4k times and dividing the clock time: > https://github.com/palisades-lakes/function-experiments/ > blob/dynesty/src/scripts/clojure/palisades/lakes/funx/l2norm/msec.clj > > These both use general benchmarking code from > https://github.com/palisades-lakes/benchtools > > The experimental metadata function wrapper is in: > https://github.com/palisades-lakes/dynamic-functions/blob/ > dynesty/src/main/java/palisades/lakes/dynafun/java/MetaFn.java > > > On Wed, Sep 20, 2017 at 11:17 AM, John McDonald <palisades.la...@gmail.com > > wrote: > >> Thanks for the quick response. >> >> One issue at a time: >> >> (A) Putting metadata on Vars instead of on the functions themselves: >> >> I need to be able to associate facts with the function instances. I can't >> rely on every function being bound to a Var. >> For example, I'm constructing cost functions for machine learning, and >> other applications, by summing, composing, etc. other functions. >> >> >> >> On Tue, Sep 19, 2017 at 11:34 PM, Alex Miller <a...@puredanger.com> >> wrote: >> >>> >>> >>> On Tuesday, September 19, 2017 at 8:01:07 PM UTC-5, John Alan McDonald >>> wrote: >>>> >>>> I'd like to be able to do something like: >>>> >>>> (defn square ^double [^double x] (* x x)) >>>> (def meta-square (with-meta square {:domain Double/TYPE :codomain >>>> Double/TYPE :range {:from 0.0 :to Double/POSITIVE_INFINITY :also Double >>>> /NaN}}) >>>> >>>> https://clojure.org/reference/metadata >>>> <https://www.google.com/url?q=https%3A%2F%2Fclojure.org%2Freference%2Fmetadata&sa=D&sntz=1&usg=AFQjCNHbXrwRkSRL6pFAprN1DcrQPUEbJA> >>>> says "Symbols and collections support metadata...". Nothing about >>>> whether any other types do or do not support metadata. >>>> >>> >>> Functions are probably the big missing thing in that list of types that >>> have metadata that you can modify. >>> >>> A few other things also have metadata that can be set at construction >>> and read but not modified after construction: namespaces and the reference >>> types (vars, atoms, refs, agents). >>> >>> >>>> The code above works, at least in the sense that it doesn't throw >>>> exceptions, and meta-square is a function that returns the right values, >>>> and has the right metadata. >>>> That's because square is an instance of a class that extends >>>> AFunction, which implements IObj (https://github.com/clojure/cl >>>> ojure/blob/master/src/jvm/clojure/lang/AFunction.java#L18). >>>> >>>> It doesn't work, in the sense that it violates "Two objects that >>>> differ only in metadata are equal." from https://clojure.org/refer >>>> ence/metadata. That is, >>>> (= square meta-square) >>>> returns false. >>>> >>> >>> I think the tricky thing here is that functions are only equal if they >>> are identical. Perhaps it would make sense to implement equals in the >>> function hierarchy to somehow "unwrap" the meta wrappers. I'm not sure if >>> that even makes sense. >>> >>> Another option here is this, which I think would be more typical: >>> >>> (def ^{:domain Double/TYPE :codomain Double/TYPE :range {:from 0.0 :to >>> Double/POSITIVE_INFINITY :also Double/NaN}} meta-square square) >>> >>> that puts the meta on the var (meta-square), not on the function >>> instance itself. In this case equality works and the invocation timing >>> should be about the same since in both cases you're going through the var >>> to invoke the identical function. You can retrieve the meta with (meta >>> #'meta-square) since it's on the var. >>> >>> For my purposes, what really matters is that calling meta-square has >>>> roughly 30 times the cost of square itself (and about 3 times the cost of a >>>> version without type hints). >>>> >>> >>> I see the overhead of your meta-square as more like 2 times the cost in >>> a quick test, not sure how you're testing. I'm using 1.9.0-beta1 and Java 8 >>> and timing 100,000 invocations over a series of runs. There's a lot of >>> variability - using something like Criterium would yield better data. >>> >>> >>>> The reason is that meta-square is an instance of a class that extends >>>> RestFn (https://github.com/clojure/clojure/blob/master/src/jvm/cloj >>>> ure/lang/AFunction.java#L26), whose invoke() methods are expensive. >>>> >>>> Also, for my purposes, it would actually be better if "Two objects >>>> that differ only in metadata are NOT equal." So perhaps I shouldn't be >>>> using metadata at all. It just seems >>>> >>>> Options: >>>> >>>> (1) Add a meta field to clojure.lang.AFunction (and fix equals and >>>> hashcode). I presume the reason there isn't already a meta field is to keep >>>> functions as light weight as possible. Are there good benchmarks that I >>>> could use to measure the cost of adding an almost always empty field? >>>> >>> >>> I think it would add the cost of an object ref (prob 32 or 64 bits) to >>> every function object and I don't think it matters if it's empty or not. I >>> don't really know the reason for the current design, would require some >>> research. There are a LOT of potential considerations here with respect to >>> backwards compatibility, etc. Any change like this would be treated very >>> carefully. I do not think the need is necessarily worth such a change, but >>> it's hard to weigh that. >>> >>> >>>> (2) Experiments with a mechanical wrapper class ( >>>> https://github.com/palisades-lakes/dynamic-functions/blob/d >>>> ynesty/src/main/java/palisades/lakes/dynafun/java/MetaFn.java) show >>>> almost no overhead, but extending that to cover every possible combination >>>> of clojure.lang.IFn$DD, clojure.lang.IFn$DLD, ..., is impractical. >>>> >>> >>> That's what code gen is for. >>> >>> >>>> (3) Use asm to create a new class that extends the original function's >>>> class and implements IObj in the obvious way. >>>> >>> >>> The asm included inside Clojure should be considered an internal >>> implementation detail, subject to version and API changes without warning. >>> >>> >>>> My short term plan is (2), ignoring the equals violation, and >>>> implementing primitive interface wrappers as needed. >>>> >>> >>> Will the var version above satisfy? >>> >>> >>>> Are there problems with (3) asm, as a long term solution? >>>> >>> >>> As mentioned above, you should not rely on this being available or free >>> from breakage. >>> >>> -- >>> You received this message because you are subscribed to the Google >>> Groups "Clojure" group. >>> To post to this group, send email to clojure@googlegroups.com >>> Note that posts from new members are moderated - please be patient with >>> your first post. >>> To unsubscribe from this group, send email to >>> clojure+unsubscr...@googlegroups.com >>> For more options, visit this group at >>> http://groups.google.com/group/clojure?hl=en >>> --- >>> You received this message because you are subscribed to a topic in the >>> Google Groups "Clojure" group. >>> To unsubscribe from this topic, visit https://groups.google.com/d/to >>> pic/clojure/D8mksieuUPI/unsubscribe. >>> To unsubscribe from this group and all its topics, send an email to >>> clojure+unsubscr...@googlegroups.com. >>> For more options, visit https://groups.google.com/d/optout. >>> >> >> > -- You received this message because you are subscribed to the Google Groups "Clojure" group. To post to this group, send email to clojure@googlegroups.com Note that posts from new members are moderated - please be patient with your first post. To unsubscribe from this group, send email to clojure+unsubscr...@googlegroups.com For more options, visit this group at http://groups.google.com/group/clojure?hl=en --- You received this message because you are subscribed to the Google Groups "Clojure" group. To unsubscribe from this group and stop receiving emails from it, send an email to clojure+unsubscr...@googlegroups.com. For more options, visit https://groups.google.com/d/optout.