HaxeFoundation / haxe-evolution

Repository for maintaining proposal for changes to the Haxe programming language
111 stars 58 forks source link

readOnlyArray optimization #68

Closed Drakim closed 4 years ago

Drakim commented 4 years ago

My proposal for a somewhat obvious optimization for the readOnlyArray. Hopefully this proposal isn't as wishy-washy and vague as my last one :)

Rendered version

RealyUniqueName commented 4 years ago

Is it that common to have a compile-time known index for array access? I think in real world most of the time indices would be calculated or passed from vars/fields, which makes it impossible to inline such access.

nanjizal commented 4 years ago

Surely Haxe already does this sort of thing without needing readonly? https://try.haxe.org/#114CB

Drakim commented 4 years ago

@RealyUniqueName Definitely! While it's true that "most of the time" the index would be indeterminate, that doesn't mean it doesn't happen, or that it wouldn't be useful to have the compiler optimize it. I found myself in need of this myself, which is why I made the proposal :D

For me, it's because I'm making a library for others, and I want to squeeze out as much performance as possible. Sometimes the programmer's input will be constant and deterministic enough that this feature would allow me to inline the code rather than do an function call from a LUT, which would be a huge boon. Especially since the Haxe compiler can often calculate and optimize a lot of stuff at compile-time (with inline constructors especially!) and end up with a constant index for stuff you wouldn't think would be constant.

@nanjizal Very interesting! I take it the compiler does this optimization because it can see in the local scope that the array is unmodified from it's creation. But is there a way to do this without having re-create the array with every function call? LUTs can become rather big to re-instantiate with every call.

Edit: I tried it out (locally, since the try.haxe.org only runs haxe version 3.4.4) and while it works fine for integers, you can't do a inline arr[0](); as the compiler doesn't dare inline a function that comes from an array access, even when it can be deterministically 100% certain of the function.

Just to clarify, inline myvar(); works fine inlined even if myvar is a field holding a function rather than a regular class method, just not if it comes from an array, thus making an inlined LUT is not possible.

nanjizal commented 4 years ago

No compiler is even smarter than that I think. To be honest I guessed and tried, Haxe does similar with matrix type custom classes and typedefs, something you maybe familiar with if you use Kha Maths, but easy to make use, I use it with geom a simple experimental math library using quite a bit of abstracts over simple property classes. Technically I believe it has been a feature within Haxe since 3.3 but progressed significantly in haxe 4. https://haxe.org/manual/cr-static-analyzer.html It's not related to local scope, as you can use it to do transforms of matrices, I have noticed it breaks down if you try to use a typedef to allow class implementation swaps even if you don't, the analyser needs to be within strict class structures, it will gives up if you use anything like a cast and no chance with dynamic and you can easily fox it, it requires clear and fairly obvious structures so it can analyse. But I am sure compiler devs can clarify this I am only guessing based on my limited experimentation.

nanjizal commented 4 years ago

@Drakim could you post a http://try-haxe.mrcdk.com/ example so I can follow better?

Drakim commented 4 years ago

I'd love to, but I think the try-haxe environment you are linking to is broken, I'm getting a "Build failure" on even the default example that comes when I load the page.

Edit: In the meanwhile, here is my example:


class Main {
    static public function main() {
        Experiment1.run();
        Experiment2.run();
        Experiment3.run();
        Experiment4.run();
        Experiment5.run();
        Experiment6.run();
        Experiment7.run();
    }
}

class Experiment1 {
    static private function foo() {
        trace('Running experiment 1');
    }
    static public function run() {
        inline foo();
        // Inline success
    }
}

class Experiment2 {
    static private final foo:Void->Void = function() {
        trace('Running experiment 2');
    }
    static public function run() {
        inline foo();
        // Inline success
    }
}

class Experiment3 {
    static public function run() {
        final arr = ['Walking experiment 3', 'Running experiment 3', 'Sprinting experiment 3'];
        trace(arr[1]);
        // Inline(?) success
    }
}

class Experiment4 {
    static private function foo() {
        trace('Running experiment 4');
    }
    static public function run() {
        final arr = [foo];
        inline arr[0]();
        // Inline failure
    }
}

class Experiment5 {
    static private final foo:Void->Void = function() {
        trace('Running experiment 5');
    }
    static public function run() {
        final arr = [foo];
        inline arr[0]();
        // Inline failure
    }
}

class Experiment6 {
    static public function run() {
        final arr = [function() {
            trace('Running experiment 6');
        }];
        inline arr[0]();
        // Inline failure
    }
}

class Experiment7 {
    static private function foo() {
        trace('Running experiment 7');
    }
    static private final arr:haxe.ds.ReadOnlyArray<Void->Void> = [foo];
    static public function run() {
        inline arr[0]();
        // Inline failure
    }
}
Drakim commented 4 years ago

Request for voting. @HaxeFoundation/evolution-voters

Edit: it appears that HaxeFoundation/evolution-voters is gone, what am I supposed to do instead?

Edit2: Request for voting withdrawn until proposal is improved

Simn commented 4 years ago

Throwing a piece of code at us and calling it "detailed design" is not how this process works. If you want this in the compiler you'll need to think about the actual design and the consequences of a change like this. At the very least you'll have to precisely define the conditions under which this optimization is applicable.

As for the feature itself, I'm not exactly against it, but it seems so narrow that I don't know if I would actually implement it at any point in the near future.

Drakim commented 4 years ago

I understand, and I have nothing against following the process and fleshing out the proposal and it's design as much as needed. In the template it said this:

Describe the proposed design in details the way language user can understand and compiler developer can implement. Show corner cases, provide usage examples, describe how this solution is better than current workarounds.

And I figured I was fulfilling that with my examples, and I didn't want to be overly verbose and make my proposal into a rant (and some of the other proposals I spied on often kept it similarly simple). Can you tell me how I could better formulate my design, should I simply write in plain English when I think this optimization would and would not apply?

Simn commented 4 years ago

Check out https://github.com/RealyUniqueName/haxe-evolution/blob/master/proposals/NNNN-abstract-classes.md#detailed-design, that's a great example of how to deal with details in a code + short description kind of way.

Drakim commented 4 years ago

Thank you, that makes it very clear to me. I'll update my proposal to that standard of quality.

Simn commented 4 years ago

We have decided to accept this proposal in our haxe-evolution meeting yesterday.

However, I will still close this PR because the proposal itself is poor and there has been no followup. Progress can be tracked in the linked Haxe issue.