fslang-suggestions icon indicating copy to clipboard operation
fslang-suggestions copied to clipboard

FSI caching

Open voronoipotato opened this issue 2 years ago • 15 comments

I propose we cache the compiled scripts for FSI so that when they are run again without any changes, it doesn't have to recompile from scratch.

The existing way of approaching this problem in F# is to use Fake's script runner.

Pros and Cons

The advantages of making this adjustment to F# are that we can use FSI for scripting.

The disadvantages of making this adjustment to F# are more work.

Extra information

Estimated cost (XS, S, M, L, XL, XXL): M? S? not sure. Depends on also if we have to cleanroom this or if the Fake team is interesting in collaborating.

Related suggestions: (put links to related suggestions here)

Affidavit (please submit!)

Please tick this by placing a cross in the box:

  • [X] This is not a question (e.g. like one you might ask on stackoverflow) and I have searched stackoverflow for discussions of this issue
  • [X] I have searched both open and closed suggestions on this site and believe this is not a duplicate
  • [X] This is not something which has obviously "already been decided" in previous versions of F#. If you're questioning a fundamental design decision that has obviously already been taken (e.g. "Make F# untyped") then please don't submit it.

Please tick all that apply:

  • [X] This is not a breaking change to the F# language design
  • [X] I or my company would be willing to help implement and/or test this

For Readers

If you would like to see this issue implemented, please click the :+1: emoji on this issue. These counts are used to generally order the suggestions by engagement.

voronoipotato avatar May 12 '22 14:05 voronoipotato

Yes please! 💯

Using a dedicated fsproj just to benefit from caching is a bit annoying, and Fake feels a bit like pulling out the big guns for very simple scripts.

I remember trying to create a library project that could retrieve FSI's generated files and reuse them, but it was harder than I thought and I gave up.

mlaily avatar May 14 '22 06:05 mlaily

Relevant article: https://blog.lucca.io/2022/05/19/fsharp-script/

voronoipotato avatar May 20 '22 17:05 voronoipotato

The advantages of making this adjustment to F# are that we can use FSI for scripting.

Could you clarify what you mean here? Executing F# code in blocks sent to a running FSI process is surely a form of scripting, is it not?

Or do you mean that you would like to re-invoke dotnet fsi against a full .fsx file as your interaction model? If that's the case then I think several things about FSI would have to change, probably starting with finding a way to reduce the very long startup time as much as possible. Caching scripts could help some scenarios, but the multi-second startup time would have to be addressed separately to improve the time until a result is computed.

cartermp avatar May 22 '22 08:05 cartermp

Not OP, but I believe the current suggestion is indeed about startup speed of dotnet fsi myScript.fsx.

The assumption is that dotnet fsi generates a compiled assembly when executing a script, and by reusing this assembly for subsequent calls (if the script did not change) instead of re-generating it, it would greatly improve startup speed. Is that not the case?

(I believe this is approximately what dotnet run and Fake do)

mlaily avatar May 22 '22 08:05 mlaily

I'm talking about the running of a dotnet fsi against a full .fsx file. The interactive mode of scripting seems fine.

voronoipotato avatar May 23 '22 14:05 voronoipotato

@mlaily I won't speculate here - the answer to "where is startup time being spent?" is going to differ from script to script and environment to environment. It would be helpful if someone did some performance analysis (specifically tracing) to capture some exemplar traces and see where total CPU time is spent during startup. But I can say that the design of FSI is not one that optimizes for quick startup time since the interaction model is to keep it as a long-running process. That typically means a lot of work done up front (e.g., there's a full msbuild assembly resolve) so that subsequent interactions are very fast.

cartermp avatar May 23 '22 14:05 cartermp

Is there a reason in this situation that using fsi with some sort of assembly caching is preferable to just using fsc to compile the script to an exe?

bisen2 avatar May 31 '22 12:05 bisen2

@bisen2 It's more a matter of simplicity and ease when dealing with scripts.

dotnet fsi myScript.fsx is very convenient (though a bit slow). How would you execute a script in a similar way with just fsc?

mlaily avatar May 31 '22 14:05 mlaily

I believe it just just necessary to do fsc myScript.fsx to compile the script, then use ./myScript.exe to run it without re-compiling.

I don't think this is a perfect solution, but in my opinion it feels orthogonal to fsi's purpose to cache assemblies on disk.

bisen2 avatar May 31 '22 16:05 bisen2

it feels orthogonal to fsi's purpose to cache assemblies on disk.

I kind of agree, but dotnet fsi is sold as the way™ to do scripting in F# out of the box, and even if it's not its primary function, it works pretty well!

In comparison, fsc is not meant for scripting:

  • It is not easily discoverable since it's not on the PATH by default
  • It cannot be used with interactive directives like #r nuget:...
  • It litters the working directory by outputting all the dll dependencies next to the generated exe

mlaily avatar May 31 '22 18:05 mlaily

I don't have too much to add here, but regarding this:

it feels orthogonal to fsi's purpose to cache assemblies on disk.

The purpose of software is always up for discussion and change 🙂. True, today FSI is not designed for the "launch and run a script real fast" scenario, but that doesn't mean it can't be shaped into something of that form.

cartermp avatar Jun 01 '22 21:06 cartermp

The F# compiler can already compile a script to make an exe. Is there something else that is required?

KevinRansom avatar Jun 20 '22 07:06 KevinRansom

@KevinRansom to answer that question, technically you can only currently compile a subset of fsx files. Additionally having to call the compiler to run a fsx performantly with a bunch of extra flags and such, completely breaks the flow and convenience of using FSI. FSI is valuable because it is simple to use and without much ceremony. The moment I'm like scrolling through the compiler flags to get something working for a small script that took 2 minutes to write, I might as well use something else. So the purpose of this suggestion is to ensure that using FSI is convenient. It would be nice avoid needing to make fsproj files just to run a build process for example.

To get into the details a bit, you would need to check and see if this had been compiled before, and presumably store the exe somewhere.

We could hash the script with SHA1, use the hash as the exe name. Then when you run fsi over an fsx, it would look for the hash.exe and if it exists would run that, otherwise it would compile, save, and run. If we store in %appdata%/fsi-cache for Windows and ~/.fsi in Posix (consistent with norms for each OS) then the scripts would remain cached even if you moved them to other folders.

We could add a --nocache argument for FSI for people who want to ensure that it recompiles, particularly useful for people using type providers. Most of the exploratory programmers have moved to notebooks though so the impact is lowered by that. Though honestly if someone is using type providers and it's possible to detect that, we may just want to avoid caching for code with TP's by default and instead have a --forcecache.

voronoipotato avatar Jun 21 '22 14:06 voronoipotato

Hmm... Would/could this also perhaps solve #13866 from dotnet/fsharp?

abonie avatar Sep 19 '22 13:09 abonie

Another thing to consider here is letting FSI take part in a compiler server system that was prototyped a few years ago. Some discussion here: https://github.com/dotnet/fsharp/discussions/11134#discussioncomment-392397

It does not help cold start times, but after the very first built on your host it makes subsequent builds significantly faster (in our manual testing of a rough prototype with Visual Studio at the time, it was over a 25% reduction in rebuild times for Fake.sln).

In principle, FSI could benefit from a compiler server as it needs to resolve references and do other similar stuff as a full build too.

cartermp avatar Oct 05 '22 14:10 cartermp