Тёмный

Fast F#: "Clean" Code Chat 

Fast F#
Подписаться 2,2 тыс.
Просмотров 4,6 тыс.
50% 1

This is a response and continuation of the ideas and thoughts put forward by Casey Muratori in his video on "Clean" Code, Horrible Performance. I want to show how to implement the algorithm using several techniques in F# and discuss their Pros and Cons. Ultimately I suggest a method that is simpler and faster than any others.
We also discuss why the techniques Casey used in his video led to better performance and what we can take away from it when writing high-performance code in F#.
Links:
"Clean" Code, Horrible Performance
By Casey Muratori
Video: • "Clean" Code, Horrible...
Class: www.computerenhance.com/p/cle...
Data-Oriented Design in C++
By Mike Acton
Video: • CppCon 2014: Mike Acto...
Practical Data-Oriented Design
By Andrew Kelley
Video: guide.handmade-seattle.com/c/...
Code: github.com/matthewcrews/Clean...
=== Contact ===
Email: hi@fastfsharp.com
Mastadon: mastodon.sdf.org/@fastfsharp
Twitter: / fastfsharp
=== Tags ===
Tags: #fsharp, #dotnet, #highperformance , #functionalprogramming

Наука

Опубликовано:

 

28 фев 2023

Поделиться:

Ссылка:

Скачать:

Готовим ссылку...

Добавить в:

Мой плейлист
Посмотреть позже
Комментарии : 62   
@ARVash
@ARVash Год назад
Clean or tidy code in F# means something entirely different than clean in C#.
@fragboy7621
@fragboy7621 Год назад
Great examples and I am happy to see that there are people that care about performance. I was wondering if you will be making videos about more advanced F# features/concepts like computational expressions/apply/binds and perhaps even the M-word?
@FastFSharp
@FastFSharp Год назад
Yes, eventually. I've been trying to create all the beginner content that I wish already existed first to help people get started with F#. From there I'll start wandering into more advanced topics.
@tullochgorum6323
@tullochgorum6323 Год назад
@@FastFSharp Once you get beyond the mystique of monads they're not really that scary - after all even newbies like me are using monads like seq, list, option and Async. Scott Wlaschin has some excellent stuff on how they work, but a fresh take would be very welcome. A clear walk through the practicalities in F# would be a valuable contribution to the community...
@greggy_b
@greggy_b Год назад
I read the blog post of this last night and immediately thought of you. I was going to send it your way, but looks like you beat me to it (:
@WillEhrendreich
@WillEhrendreich Год назад
this is super helpful, thanks. having your stuff as a "translation layer" between what Casey and Mike and Johnathan Blow are talking about is pretty great, as I am not familiar enough with C to meaningfully interpret it into FSharp land.
@FastFSharp
@FastFSharp Год назад
You caught me! My entire channel is basically just a "translation layer" between game engine techniques and F# 🤣
@WillEhrendreich
@WillEhrendreich Год назад
@@FastFSharp I don't mean that to sound reductionistic, at all! I hope you're not taking it like that! I'm saying what you're doing is so freaking valuable because so few of us dotnet folks are even used to thinking this way, much less trying to adopt techniques from the game dev side of code. I don't know if you've searched for game dev in FSharp stuff but it's almost non-existant. there is a game engine called Nu that supposedly takes an elmish style MVU approach, and a few people mention that you can use dll's created in fsharp as imports into unity.. Aside from that there is honestly not a whole lot. We don't get fed these kinds of concepts in learning materials for FSharp. If you go over to the learning materials of the Csharp side of dotnet, it is absolutely FLOODED with the OO style Enterprise Dev From Hell practices that are just so difficult to even reason about it's dizzying. So to actually get someone taking performance code seriously on our side is super vital, and while I don't have the same critical hot path code concerns most of the time, there are situations where I can definitely see benefit because of this understanding. A lot of it is actually low hanging fruit, like you point out at the end of this video, if we simply don't force the cpu to miss branch predictions all the time, we gain absurd efficiency for almost no effort. I wouldn't have thought of what you did at the end, more than likely, because I'm not yet used to thinking about what would actually cause cache misses, and it's very eye opening.
@FastFSharp
@FastFSharp Год назад
@@WillEhrendreich I didn't take it as reductionist at all 😊. At the same time, I want to cite my sources and let people know "all I'm doing" is translating what other, far smarter people, have done. There's a desperate need for the learnings of the gamedev space to be brought to the .NET space.
@islandman9619
@islandman9619 Год назад
So, this is definitely interesting, but only relevant in some areas - as always. 1) Make it work, 2) make it fast, 3) make it pretty, where 2/3 can change order depending on priorities. In our work, the number one concern is maintainability. Keep in mind, and this is where a lot of devs fail: in an enterprise, more likely than not, you're not writing code for you but for other devs to maintain. In addition, for us, US Telecom industrry, for internal and external applications, 99%+ not requiring performance optimizations, clean code is paramount to lower the total cost of ownership and time to market. Hence, performance is only an issue when it materially affects users' ability to complete their tasks and I would guess, for the vast majority of applications, this is not a concern. Anyway, good work as always as I'm going to resume my work writing an F# 2D game engine, which will need this type of insight. Cheers!
@brucejdragon
@brucejdragon Год назад
Great video. I had no idea the memory layout of struct DUs was so inefficient! It would be interesting to see the benchmarks around creating and storing the shapes before performing calculations on them. There is a common tradeoff in databases between the performance of reads and writes (indexes imply faster reads but slower writes - Elasticsearch is an extreme example of this). The final separate shapes approach looks like another example of this tradeoff.
@ProjectVastness
@ProjectVastness Год назад
I've never been a guy that done web stuff, never. I worked with C/C++ , Turbo Pascal, Cobol, and other "weird" && || old languages . Ofc I know a little bit of almost all modern languages. And I must say even though I'm not a microsoft fan, I love the way F# is. And I'm a total noob at the language. I'm also a noob in APL but maybe I know more from APL than from F# for sure. I've done all the free learning stuff from F# in microsoft website , but still I don't know how to build something with F# . I woul love to know more. But coming from C and all the differences I see F# as a fast language, ofc is not C/C++ or Rust or even ZIG, but I agree with the performance stuff mentioned in the Original video you are also commenting. I'm doing a free course at HPI platform about energetic efficiency & coding, its also awesome. You need to see that also. ^^
@svenbardos6637
@svenbardos6637 Год назад
Does the order of patterns in a "match with" affect the runtime? If I know that a case is more likely to happen, should I test it first? Or is the compiler mixing it up anyways?
@FastFSharp
@FastFSharp Год назад
Not that I am aware of. I haven't tested it either so I can't say for sure. The compiler does a lot of magic when it comes to compiling a `match with` statement.
@user-xi7ig1ez8m
@user-xi7ig1ez8m Год назад
Don't feel obliged to react, I just want to say that I (as many others) liked this video.
@svenbardos6637
@svenbardos6637 Год назад
In imperative programming a lot of performance gains come from interrupting when a criteria is met and it doesn't make sense to compute any further. So "continue for", "break", "return" are your best friends. How would I do that in functional programming? What patterns do you use to shortcut things? Or is that not neccessary?
@FastFSharp
@FastFSharp Год назад
You can achieve this with a local recursive function. That's a common pattern in F# code. The compiler knows this is common so it actually turns that local recursive function into a while loop so there is no performance penalty for using it.
@Noble_Savage
@Noble_Savage 9 месяцев назад
I don't know F#, but this video was very enlightening, thanks!
@FastFSharp
@FastFSharp 9 месяцев назад
Glad it was helpful!
@WarrenLeggatt
@WarrenLeggatt Год назад
Really good take on the "clean" video. The whole subject is going to be a battle online I think lol. I think the reason the indexed lookup of an enum causes the "mental tension" is that it is just unsafe. Not sure the F# but assume similar to c# under the hood in that I could just pass in say 999 cast as the num and then it would de-ref the array array[999] = BANG in the best case. It will always be faster with the lookup, the standard speed up a pure function is translate to a lookup if possible. I did not realise DU was a ref type and the way it stores in f# with mem for all cases is a little dumb. The trouble with the "smart" work arounds is type safety and other devs coming in and not picking up the subtle stuff like this which causes more bugs.
@FastFSharp
@FastFSharp Год назад
I really want to "fix" the struct DU implementation in F#. I think the reason it hasn't been is because it would never work for Generics. The optimization will only be possible in cases where the types for all of the cases are defined. Now, all of my scenarios fit that definition so I would get a lot of value out of it. There are attempts to fake this but they all involve some "magic".
@WarrenLeggatt
@WarrenLeggatt Год назад
@@FastFSharp i guess i do not know much of f# deep down to know why the mem structure effects generics. The data is immutable so would have though using max size of biggest case would do. Might be other technical debt though causing. I was surprised with you mut loop arrays. I guess idiomatic is map/reduce but mut for speed?
@KoziLord
@KoziLord Год назад
​@@WarrenLeggatt The issue with the layout has to do with the Garbage Collector. If you try to overlap all variants of an union you could end up with a case where (for example) your Vector2 lives in the same memory as an object reference, which the GC will not pick up on as it doesn't understand unions. Therefore it will try to GC the Vector2 and break. There's cases where that isn't an issue, but it's not possible to decide at F# compile time when using generic parameters with the union. In cases where all the types of a union are known however you can specialise for cases where all the variants are ref types, or they're all unmanaged structs (no object references in fields).
@WarrenLeggatt
@WarrenLeggatt Год назад
@@KoziLord ah. Thanks for that
@maxpaige6688
@maxpaige6688 Год назад
Do you have this in a repo? I want to plug in the way I'd naturally write this initially and see how bad it is.
@FastFSharp
@FastFSharp Год назад
Here you go: github.com/matthewcrews/CleanCodeDiscussion You'll need to run your Terminal as Admin to get the hardware counters FYI.
@maxpaige6688
@maxpaige6688 Год назад
@@FastFSharp Thank you!
@FastFSharp
@FastFSharp Год назад
@@maxpaige6688 Enjoy! Let me know what you find 😊. Let me know if I did something foolish 😂.
@JonathanPeel
@JonathanPeel Год назад
While I do find this very interesting, I do think your continuous emphasis on "Hot Loop" is very critical here. I find in most (maybe even all) of my work, the "Standard F# way" is more than good enough. Working with 1,000,000 objects, and 3 or 4 is very different.
@Grimsleeper86
@Grimsleeper86 Год назад
Ya, and I am gonna make the rubyist argument of optimizing our time has value too. There are some basic principals that get you real far with little effort, like minimizing costly network requests or caching/memoizing the unchanging that will get you real far on their own. Even if we concede that you should spend time making things as fast/efficient tIme as possible, make sure you analyze that you are spending your own time on what is most impactful. Another way to put it, its okay to stop optimizing once you have hit your goals. Sometimes those goals are 5 requests per minute, sometimes its 1k ops per second.
@FastFSharp
@FastFSharp Год назад
True, but I don't want people to miss Casey's point that software today is abhorantly slow. It's unbelievably slow when you consider the power of our CPUs. Yes, our software may be "fast enough" if it runs in 10ms. But let's say it could run in 1ms. If everyone has this attitude then everything is taking 10x the resources that they should leading to 10x the cost and 10x the energy wasted. What I'm hoping to show is that just spending a small amount of time refactoring we can achieve massive performance gains. It requires us to be introspective though and ask whether our methodologies are harming us or not. I don't want that to get lost in the discussion.
@JonathanPeel
@JonathanPeel Год назад
@@FastFSharp I like this. I think, maybe, we should also consider where this is running. If it is processing data on a web API, lets leave it a little slow and add some metrics to it. It is easy (often enough, anyway) to update the server next week if something is to slow. If this will be running on a users device, they might not install every update, memory could be limited, because we don't know what budget Android they have, and we have one chance to show them our application before they uninstall saying it's too slow. As an introspective, as you put it, I also like to be aware of the alternatives. If I am doing it the slow way, at least I know it is "the slow way". Rider is also pretty cool with this because it highlights things where you might have unknown allocations, etc.
@tullochgorum6323
@tullochgorum6323 Год назад
A valid point, but when you need this lower level understanding you *really* need it. I'm currently porting a trading application to F# and in backtesting it's processing hot loops of literally billions of ticks. So this kind of insight is very useful for these specialised domains.
@JonathanPeel
@JonathanPeel Год назад
@@tullochgorum6323 Absolutely. But trading is also a domain where users are going to notice the milliseconds. Every spinner causing a heartburn...
@JonathanPeel
@JonathanPeel Год назад
Also, if we are looking for trying to "optimize the shit out of this" approach, I would be interested to see how changing the last one from "for in" to "for to" (and then using "area rectangles[i]") affects the final results.
@FastFSharp
@FastFSharp Год назад
It’s generally slower due to additional bounds checking. The CLR is very good at optimizing for…in loops. My Interface video discusses that.
@KamillaMirabelle
@KamillaMirabelle Год назад
A DU with only tags (enim without numbering) the gethashkey() return the case number starting at 0.
@FastFSharp
@FastFSharp Год назад
Oh! I didn't know that. Now that is some useful information. Going to go run some benchmarks now...
@KamillaMirabelle
@KamillaMirabelle Год назад
@@FastFSharp i use it in a lexer interpreter backed by a deterministic finite automata. I was bored at a programming language implementation course.. and the FslexYacc are both dependent on you knowing about project file format and was slow.. I managed to make a lexer which uses a (2^32 * 2^32) + 4*8 bytes as table size. Did make a LR(1) parser too which uses only tag DU to describe productions and token types, which are used as index through hashkey. but that still have some troubles. My ideas was to embed the whole lexer and parser into F# as a lib such that it is F# code all the way, it can type check. The only problem is that is need to generate the tables each time the program runs.. but it in O(n) with a small constant do to small number of cash misses and tight code. It is NOT production grade, nor will it ever be.
@XKS99
@XKS99 Год назад
I'm sad he has comments turned off on his video.
@FastFSharp
@FastFSharp Год назад
Oh, it would be hilarious. If you want to see vitriol, you can check out the HackerNews comments.
@Grimsleeper86
@Grimsleeper86 Год назад
@@FastFSharp 832 comments, uhh no. I already block places like reddit outside of 6pm to 8pm for my health and wellbeing.
@FastFSharp
@FastFSharp Год назад
@@Grimsleeper86 To be clear, I'm not say it's worth reading. I'm just saying it's humorous because you get the exact kind of discussion that you would expect there. I honestly had to stop because I got so discouraged. I hope to create a place for healthy discussion on this channel. I want to be open to critique and feedback. If I've said something foolish, unkind, or ignorant, please let me know. I want to grow a healthy F# community and not by gaining attention through rage-bait.
@Grimsleeper86
@Grimsleeper86 Год назад
@@FastFSharp Oh sorry, I fat fingered an "uhh oh" into "uhh no" and came off a bit too harsh. Your content/comments are great, and it's wonderful to see the video series around something I have always felt is a strength of fsharp, that you can do a mix of styles to make things better according to their own needs.
@FastFSharp
@FastFSharp Год назад
@@Grimsleeper86 No worries 😊. I always want to be open to feedback. One of my biggest fears is being dogmatic and not being open to change. I personally believe that a language even better than F# will come along some day. I don't want to be dogmatic. I want to solve problems and make people's live better 😊.
@KamillaMirabelle
@KamillaMirabelle Год назад
Indifferent to the language, clean code can have a BIG performance hit, but it is not the clean code style that courses the performance penalty, but the abstraction over which you code.. if the abstraction are ill fit to the problem, the language or both it will perform badly
@FastFSharp
@FastFSharp Год назад
Agreed and I hope I made that point. Style isn't the problem, badly expressed algorithms are. For code to be fast, the CPU needs to be able to reason about it.
@KamillaMirabelle
@KamillaMirabelle Год назад
@@FastFSharp Yes and your code are only as good as your abstraction
@georgeokello8620
@georgeokello8620 Год назад
​@@KamillaMirabelle I would argue that in modern times for most devs, they have been indoctrinated to add more abstractions and the fact that there aren't even barriers to put on ourselves to compare and analyze other options to write programs to pass data across various boundaries other ways before going to abstractions as a last resort, it even gets crazy as we replicate patterns absurdly in other code bases of of fear of "predicting the wrong future use cases"
@KamillaMirabelle
@KamillaMirabelle Год назад
@@georgeokello8620 first of all, i did not say more abstraction are better.. just that the right abstraction will make your code fast. F.x. treating nodes and edges as ints if possible are an abstraction over integers.. this would do better in most cases where the relations between nodes are the most importance thing.. abstractions do not need to be extremely high level to work.. When you abstract something what you always should do, is think about what the abstractions brings to the table of simplicity.. the right abstraction can simplify condition checking which are one of the biggest overhead in performance do to wrong branch predictions.. Second most programmers use OO orientated design, which today mostly consist of trying to bringing functional abstraction into a ill suited paradigm.. i.e. trying to merge 'separation of data and behaviour' with the OO paradigm which connect data and behaviour together.. No i'm not a fan of OO. It was been well established that OO is the ONLY paradigm which has given no benefits to programming.
@webwarrior0
@webwarrior0 Год назад
I don't agree with the guy in video that you are referencing. First of all, to be clear about terminology. "Clean code" principles that he states are OOP best practices. Not all of them can be applied to other paradigms. The problem in question is quite weird. I mean, who defines a triangle by its width and height? If triangle was defined by 3 points, it would take much more space, and area calculation would be quite different from other shapes. And he couldn't use his optimizations. Or what if your application has plugins that can provide other shape types? Then inheritance is the only option. I'm not even talking about things like code maintainability or testability. These things are hard or even impossible to measure precisely, but that doesn't mean that they are not important. "Programs are meant to be read by humans and only incidentally for computers to execute." - Harold Abelson Well, sometimes performance gain is worth all the tradeoffs. But most of the time, it's not.
@FastFSharp
@FastFSharp Год назад
> Well, sometimes performance gain is worth all the tradeoffs. But most of the time, it's not. I'm not going to disagree with you but I think that we always need to consider what kinds of problems we are trying to solve. Most of the time I am concerned about performance so it's worth it for me. If that's not the type of development you are doing, great! I think it's valuable for people to be made aware of the impact they are having on performance though. Most of the time developers are completely ignorant.
@APaleDot
@APaleDot Год назад
I think one problem is thinking that he is doing "optimizations". He is simply refusing to hide his implementation behind an abstraction. That's not so much an optimization as a code style, and the claim is that it's faster by default, but also it allows you to optimize later.
@benttranberg2690
@benttranberg2690 Год назад
I totally disagree with the views in that other video. There is no conflict of some sort between "clean coding" and hardware evolution. On the contrary, it's a cooperation, where each party serves the other. The reason we write highly maintainable code - taking advantage of features in high-level languages - is simply that it pays off. His arguments ultimately boil down to just one thing - that it doesn't pay off! What a fantastic conclusion! No, we are not throwing away years of hardware evolution. We are deliberately exploiting it, for economic reasons. The software industry is paying the hardware industry to make better hardware, so that the software industry can make better software (e.g. F#), in order to make better software. The alternative would have been legions of C developers making very little progress and lots of crappy code. There is nothing wrong with writing fast code, but it's not mainstream.
@FastFSharp
@FastFSharp Год назад
I find that Polymorphic Inheritence is a not good tool for creating maintainable code. I have difficulty with it. If you find it useful, great! I for one find a DU based approach for abstracting over Shapes to be more intuitive. I think the point Casey was trying to make though is that thoughtless adherence to programming paradigms leads to horrible performance. I agree with that. I think Inheritence is especially dangerous when it comes to performance because it hides how branch-filled your code is. What's worse is that a profiler will rarely tell you that you are spending lots of time on branch-mispredictions.
@georgeokello8620
@georgeokello8620 Год назад
I suspect that you are muttering that whole point without even doing a comparative analysis yourself of "Clean Code" methodologies vs trimmed down code with the most minimal amount of abstractions as possible. Even the so called maintainability argument that most devs get from over pursuing "Clean Code" even among Senior devs I've worked with in my 8 years of industry experience has mainly shown the addiction to abstractions which almost no culturally disciplined dev development has at least put a clue on limitations or questionable applications of applying novel "patterns". This even gets worse when the foundation of the code is already built that isn't optimal eventually developed into a micro services project with 100 million + people transacting billions of times where the "performance is for C devs" no longer flies as latency in communicating in io boundaries get magnified exponentially with each request. Not to mention dealing with transmitting real time data for medical devices to do image processing to neutral networks and insuring no data losses for correct diagnoses with well designed API and protocols.
@georgeokello8620
@georgeokello8620 Год назад
Also devs can't get away with shift blame to business for delivery of terrible software as devs are getting paid to have agency in designing these systems and especially management is just a product focused organization where the freedom to design the systems are at their most optimal. We just happen to make development decisions where we make software that users may suffer negative consequences at a later period. Not to mention that the so called maintainability arguments for hard core clean coffee fall apart when too many abstractions meets with delayed consequences of not reducing code surface area as possible. Those consequences in most code bases of organizations typically become another devs and users problem by the time the dev teams vacated the project to some other organization
@brucejdragon
@brucejdragon Год назад
I agree that “clean code vs. performant code” is a false dichotomy. At the same time, a lot of “clean code” practices from the OO world are over-applied and not always necessary in a functional-first language like F# (thanks to things like immutability by default and exhaustive compile-time checking of match statements). It would be interesting to take the exercise in this video one step further to include designing the public API for this “shape calculator”. Things like abstraction and encapsulation matter at the boundary of the system, but much less so in the internals (provided those internals are maintained by a small-ish team). Like any efficient database or web service, the API for the fastest shape calculator will be batch-oriented, treat data as just data (not necessarily objects), and won’t violate any kind of encapsulation. Clean and fast!
@rabbitcreative
@rabbitcreative 8 месяцев назад
> There is nothing wrong with writing fast code, but it's not mainstream. LOOOOOOOOOOOOOL. Nothing wrong with morality, but it ain't mainstream.
@juliankohler5086
@juliankohler5086 5 месяцев назад
You kept saying "43 million cycles per shape". It's honestly a bit confusing. I hope you take this as constructive criticism, 'cause I'm interested in F# and I feel like this channel would be an asset. But for that to be true, you have to edit your videos a little better. Add a note on screen during editing, or, an eve better approach, redo that particular section. Thanks for the interesting video.
Далее
Fast F#: Abstraction Addiction
29:00
Просмотров 1,3 тыс.
Fast F#: Intro to Options
23:30
Просмотров 666
Нашли Краша Младшей Сестры !
23:46
The Business of the F# Programming Language with Don Syme
1:02:17
Fast F#: Intro to Active Patterns
25:21
Просмотров 1,4 тыс.
F# for Performance-Critical Code, by Matthew Crews
1:03:23
Why F# Works in the Enterprise
58:12
Просмотров 4,3 тыс.
Fast F#: Anonymous Records
17:02
Просмотров 568
I Interviewed Uncle Bob
1:11:07
Просмотров 320 тыс.
Premature Optimization
12:39
Просмотров 769 тыс.
899$ vs 360$ which one will you choose ? #iphone #poco
0:18