Code generation: It seems to me this has to be done on demand, to be practical.
To support every possible combination of 'implements clojure.lang.IFn$DD, clojure.lang.IFn$DLD, ...' for differing arrities, I get 13,108,878 classes. Or is there a better way? On Wed, Sep 20, 2017 at 12:05 PM, John McDonald <palisades.la...@gmail.com> wrote: > 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/blo >> b/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/dy >> nesty/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.