Push ifs up and fors down: The idiom, its algebra, and its limits

(debasishg.github.io)

81 points | by speckx 4 hours ago

12 comments

  • hatthew 2 minutes ago
    Are we talking about this from the perspective of CS (algorithm optimization) or SE (code design)?

    From an SE perspective, make a flatmap function that explicitly handles Collection<Optional<Walrus>>. The implementation doesn't matter. If your language/framework already has a compatible flatmap function, make a single frobnicate(Optional<Walrus>) function that returns whatever value is necessary for flatmap(frobnicate) to discard them.

    From a CS perspective, doing a filter from Collection<Optional<Walrus>> to Collection<Walrus> is probably a bad idea. If your collection is small, nothing matters. If your collection is large, you probably don't want to spend time making a new copy of it. If your filter just returns a view rather than a hard copy, then there is no optimization benefit and you should just do whatever makes the most sense from an SE perspective. If frobnicate is cheaper than a branch prediction failure then you're paying the branch prediction failure tax anyway regardless of when you frobnicate, and if frobnicate is more expensive then your branch prediction failures don't matter. Either way, you probably don't want to spend time making a copy.

    These are all generalizations based on hypotheticals and there are certainly a lot of exceptions, but broadly speaking I don't see a strong argument here. If optimization matters then optimize based on your own profiling of your situation, and if optimization doesn't matter then design your functions based on what features and paradigms are available/common in your area.

  • ivanjermakov 1 minute ago
    Save some time and read the original post instead: https://matklad.github.io/2023/11/15/push-ifs-up-and-fors-do...
  • socializer 3 hours ago
    I am continually impressed by the ability of LLMs to take trivial ideas and turn them into lengthy and obtuse blog posts with unnecessary analogies.
    • robofanatic 37 minutes ago
      And also generate a shorter version.
      • mathisfun123 34 minutes ago
        Semantic compressor and decompressor
    • swiftcoder 3 hours ago
      Honestly, this just looks like one of those lingo-heavy-but-surface-level blog posts that used to make functional programming spaces so insufferable to everyone on the outside
      • ahartmetz 45 minutes ago
        You want to avoid branches in hot paths. If you branch inside the loop, lots of branches. If you branch outside the loop (into different specialized loops), few branches. Big fucking deal.
      • mahboi 2 hours ago
        These things are so divorced from the reality of programming, even when they involve actual code instead of fancy lingo. Like in Scala, not a pure functional language, tutorials used to find the most convoluted higher-order functional way to do simple things.
    • bioneuralnet 3 hours ago
      Yet another encroachment on traditionally human activity.
      • moritzwarhier 3 hours ago
        I am the

          Option<Walrus>
        • msdz 1 hour ago
          I know we’re not supposed to comment just for that, but this might be my single favorite joke comment I’ve ever read here. Good job.
      • jibalt 1 hour ago
        Except that TFA is a bog standard example of traditional human activity and the GP's comment is nonsensical trolling.
    • jampekka 1 hour ago
      I'm continually impressed by the ability of humans to spam low-effort whining about suspected LLM writing in almost every HN thread.
      • socializer 58 minutes ago
        It's an LLM-generated article.
        • jampekka 49 minutes ago
          How do you know? Doesn't read particularly LLM written to me, and even if it is, it's quite well written.

          The author has been blogging about this kind of stuff for over 20 years. I'd be surprised if they suddenly let bots autonomously spam their blog.

          • raphman 33 minutes ago
            Yeah, sounds human-written to me, too. Out of habit I checked with Pangram - which identifies some parts as AI-written. (I think that might be false positives but am not 100% sure.)
  • gorgoiler 1 hour ago
    Erm, no? You write f(w: Walrus) -> Walrus and then let the caller handle Walrus|None and Iterable[Walrus] however they wish!

    And if someone decides the codebase needs an abstraction over (and therefore specific functions to handle) Iterable[Walrus|None] then you check the weather and suggest they take a break and go for a stroll. (You check the weather to see if you should lend them your brolly.)

    What am I missing?

  • ninalanyon 2 hours ago
    I've done this for years. Not every time of course but where it makes the code easier to understand and maintain.

    Speed was almost never the reason.

    • alterom 2 hours ago
      I take it you never rewrote a Matlab for loop as a vector/matrix op for insane speedups then :)
  • dieselgate 1 hour ago
    Didn’t see it mentioned in the article but isn’t leading with if-statement called a “guard clause”. I like that pattern but it’s just general best practice I thought.
  • wallstop 2 hours ago
    What is missing here is any benchmarks backing up this argument for code structure.

    Of note, as of C#9 (and maybe prior), the dotnet runtime does this automatically whenever it is deemed safe. https://devblogs.microsoft.com/dotnet/performance-improvemen...

    The same technique is applied as an optimization, when deemed safe, in all current gen c compilers (gcc, llvm, etc).

    I'm very confused why neither measurements nor references to when this is done automatically in most modern languages is included in the article.

    • cogman10 2 hours ago
      At least in JVM land, it's pretty easy to thwart that optimization. Particularly if the condition is on a mutable yet unchanged in the loop value.

      For example:

          var map = new HashMap<String, String>();
          map.put("foo", "bar");
          for (var i : items) {
            if ("bar".equals(map.get("foo")) {
              doStuff(i);
            }
          }
      
      Even though `map` isn't mutated, it's hard enough for the JVM to detect and the underlying `get` functions are complex enough that it'll run the `get("foo")` every time, which can be quite expensive.
    • Maxatar 1 hour ago
      Can't speak for C# but in C/C++ the optimization can rarely be applied safely due to aliasing. If any part of the data you're working with involves a char* then C/C++ optimizers refrain from doing these kinds of optimizations because of how difficult it is to guarantee the absence of mutability.
  • throwawayffffas 1 hour ago
    Just the branch predictor gains are probably worth it.
  • aappleby 2 hours ago
    I have always phrased this as "Never do one of something".
  • OutOfHere 2 hours ago
    I like it, but to do fizzbuzz in this way, you'd have to separate what's inside the loop into a reused function.
  • alterom 2 hours ago
    TL;DR in one sentence:

    "the loop runs without a branch, and is a candidate for vectorization".

    That's it, that's the article. This matters a lot in huge-scale / scientific computing / HPF, where if you can express something as an operation on vectors on matrices, you win big (those ops parallelize well, can be run on GPUs, clusters, what have you).

  • apefulsin 2 hours ago
    [dead]