Uncurry optimization · Issue #479 · purescript/purescript · GitHub
Skip to content

Uncurry optimization  #479

Closed
Closed

Description

@Fresheyeball

I made this jsPerf as a test http://jsperf.com/curry-cost

The automatic currying of functions is an awesome part of this language and functional in general. But in javascript it comes at a serious cost (particularly if your paradigm is to use shit tons of function application).

Some languages (Fay and Elm) flatten out curries at compile time if they are not used. So

  add3 x y z = x + y + z
  trace 1 2 3

instead of becoming:

   var add3 = function(x){
         return function(y){
               return function(z){
                      return x + y + z;
               };
         };
   };
   trace(add3(1)(2)(3));

becomes:

 var add3 = function(x,y,z){
       return x + y + z;
 };
 trace(add3(1,2,3));

are there any plans to add optimization around currying? I believe this could be done without breaking changes.

Activity

  1. garyb commented on Jul 13, 2014

    @garyb
    Member

    @paf31 did some work on this here, however the result was mixed to say the least... it seemed to be slower than the curried version when we tried using it on purescript-in-purescript, in v8 anyway. We'll probably look into it again yet.

    We currently have mkFn2, 3, 4... and runFn2, 3, 4... helpers in Prelude that we're going to add compiler support for in the near future (probably this week), but they'll have to be used manually rather than applied automatically.

  2. added this to the 0.6.0 milestone on Jul 13, 2014
  3. Fresheyeball commented on Jul 13, 2014

    @Fresheyeball
    Author

    Slower? That's actually surprising.

  4. garyb commented on Jul 13, 2014

    @garyb
    Member

    Yeah, it definitely shouldn't be, so we need to spend some time on it to work out why. It could have been interfering with the optimizer or something, as that also runs on the JS AST at the moment.

  5. paf31 commented on Jul 14, 2014

    @paf31
    Contributor

    I'd like to avoid automatically uncurrying functions if possible, but rather work on the Data.Function library in the optimizer, and have developers explicitly type their multiple argument functions.

  6. Fresheyeball commented on Jul 15, 2014

    @Fresheyeball
    Author

    What do you mean by "have developers explicitly type their multiple argument functions." ?

  7. paf31 commented on Jul 15, 2014

    @paf31
    Contributor

    @Fresheyeball I want developers to opt in to multiple argument functions by using mkFnX and runFnX from Data.Function.

  8. Fresheyeball commented on Jul 15, 2014

    @Fresheyeball
    Author

    @paf31 that makes sense from a 'writing code in purescript' perspective. But there is a very real performance cost to all the currying (see OP). All I am suggesting is a psc compiler flag to unroll curried functions as much as possible for performance. The default would be without this optimization of course.

  9. puffnfresh commented on Jul 15, 2014

    @puffnfresh
    Contributor

    Curried functions in JavaScript create a new function object for each intermediate function application. This has a cost in current JS virtual machines. I don't know any which can optimise it away, yet.

  10. paf31 commented on Jul 15, 2014

    @paf31
    Contributor

    Well the multi arg functions stuff is now optimized in the Haskell compiler, so feel free to give that a try and see how that helps.

    I'm closing this as a duplicate since the functionality is covered by multi arg functions, and I don't particularly want to uncurry anything automatically, as it makes the mapping from PureScript to JavaScript less explicit.

  11. 17 remaining items

  12. paf31 commented on Jul 15, 2014

    @paf31
    Contributor

    How does it not affect predictability? "A function becomes a JS function of one argument" (simple) becomes "a function becomes a JS function of one argument except when all of its call sites across all modules are fully applied" (not even modular).

    Also, I don't see any rationale for your statement "only valuable as an application wide optimization".

  13. Fresheyeball commented on Jul 15, 2014

    @Fresheyeball
    Author

    Its only valuable as an appication wide optimization because the vast majority of code is curried. A version that has a limited impact via 'mkFn' does not address the real performance cost.

    I agree that an uncurry optimization adds complexity, but more complex does not necissarily mean less predictable. My conception of how to impliment is not intended as perscriptive, there may be a simpler way to have the same benefit. It sounds like there is already a branch for this. Could I get sample output from that branch?

    I may be able to help identify why the performance improvement did not materialize.

  14. garyb commented on Jul 15, 2014

    @garyb
    Member

    Actually, I just tried it out again. uncurried is ~200ms faster than the master of the same age on ps-in-ps after all... But, contrast that with ~750ms out of it by something much simpler - removing {} values for empty __superclasses values. There are almost certainly other things we should worry about before we start looking at the cost of function applications, even after the prototypes-with-typeclasses-and-misc-optimizations branch gets merged.

    Looking a the jsperf results for cost in v8, it's 56% slower than uncurried, but we still 45m ops/sec out of that, it's not exactly slow. The Firefox case is interesting, as curried is 96% slower than uncurried, but then it's also a lot faster than v8 at function application... in fact, curried is almost as fast as uncurried in v8.

    My approach of partially applying at call sites would fix the modularity issue, but I'm not sure if it will be as performant as what uncurried produces.

  15. garyb commented on Jul 15, 2014

    @garyb
    Member

    @Fresheyeball you can check out the uncurried branch here: https://github.com/purescript/purescript/tree/uncurried

  16. Fresheyeball commented on Jul 15, 2014

    @Fresheyeball
    Author

    Ok interesting. Im going to dive deeper.

  17. paf31 commented on Jul 15, 2014

    @paf31
    Contributor

    Fair warning: I made the uncurried branch as an experiment, I'm not happy with it, and it will take a lot of convincing to get me to merge it to master.

  18. Fresheyeball commented on Jul 15, 2014

    @Fresheyeball
    Author

    Warning appreciated. ;) I am just curious about the performance. If it really truly turns out that in a real world use case, the cost of function returns is as negligible as @garyb made it sound, then there is no need for the optimization.

  19. mgmeier commented on Apr 8, 2016

    @mgmeier
    Contributor

    Something progress has been made on this end: We've implemented an "uncurry" optimization in psc-bundle, which seems a more suitable place for it than the compiler itself.

    If you're interested in the topic, here's the read-up: http://symbolian.net/2016/04/07/513
    and this is the branch to check out: https://github.com/jutaro/purescript/tree/BundleAndOptimize

    Please do confirm if the optimization works for you, or share figures showing that it doesn't.
    Our interest is to get this as an option marked experimental into the mainline bundle tool some time soon, if the community agrees it's the right way to go.

  20. paf31 commented on Apr 10, 2016

    @paf31
    Contributor

    I think it makes a lot more sense to implement this inside psc-bundle. If we can gain confidence that it works as intended (the JS AST is a bit unstructured, after all), then I think we would be interested to merge it into master.

  21. Fresheyeball commented on Apr 10, 2016

    @Fresheyeball
    Author

    Oh my god yes!!! Make PureScript perform!!! This is HUGE!
    On Sun, Apr 10, 2016 at 11:47 AM Phil Freeman notifications@github.com
    wrote:

    I think it makes a lot more sense to implement this inside psc-bundle.
    If we can gain confidence that it works as intended (the JS AST is a bit
    unstructured, after all), then I think we would be interested to merge it
    into master.

    —
    You are receiving this because you were mentioned.
    Reply to this email directly or view it on GitHub
    #479 (comment)

  22. philipcunningham commented on Oct 12, 2016

    @philipcunningham

    For anyone else following along, the new PR is at #2219.

  23. Pauan commented on Feb 4, 2017

    @Pauan

    If anybody is still interested in this, rollup-plugin-purs now does both uncurrying and inlining. It can't uncurry every function, but it can uncurry most functions.

  24. jamesdbrock commented on Sep 26, 2021

    @jamesdbrock

    Something progress has been made on this end: We've implemented an "uncurry" optimization in psc-bundle, which seems a more suitable place for it than the compiler itself.

    If you're interested in the topic, here's the read-up: http://symbolian.net/2016/04/07/513
    and this is the branch to check out: https://github.com/jutaro/purescript/tree/BundleAndOptimize

    Please do confirm if the optimization works for you, or share figures showing that it doesn't.
    Our interest is to get this as an option marked experimental into the mainline bundle tool some time soon, if the community agrees it's the right way to go.

    It seems that @mgmeier 's Symbolian read-up on uncurrying has move to here: Optimizing Purescript´s Runtime Performance

  25. JordanMartinez commented on Sep 28, 2021

    @JordanMartinez
    Contributor

    I have to agree that having an option that optimizes all functions to be uncurried as much as possible would be nice. During development, that likely wouldn't be desirable because debugging the outputted JS would be easier. However, if one is going to minify the outputted JS anyway in production, then performance and not readability is generally their concern. Also, if this is an option, then even those outputting code in production may choose not to use it whereas those where it's a fundamental business concern can use it.

    At the very least, I think this is something that should be further discussed.

  26. rhendric commented on Sep 28, 2021

    @rhendric
    Member

    Given that functions using the primitives from Data.Function.Uncurried are currently optimized in this way, I have the impression that the best way forward would be to finish #4092 and then anyone can implement this as an external, whole-program CoreFn transformation that inserts the appropriate mkFn*/runFn* calls.

    (This plan has the downside that, I think, we don't yet know what the pitfalls are in managing an ecosystem of external CoreFn transformations. I think we all expect that it'd be easier than managing a collection of built-in compiler options/extensions, but from a distance the two approaches have enough similarity that I could imagine they might share some of the same problems.)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

      Milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions