It is very tricky to program with such a data representation. It is so error prone that, despite the performance edge, our empirical observation is that even very dedicated C programmers don’t do it.
Isn’t this what the data-oriented design people do?
What I like about this API is that it turns a highly error-prone endeavour, programming directly with serialized representation of data types, into a rather comfortable situation. The key ingredient is linear types.
Yeah, that’s cool, but at what cost? Compare add1 in the post vs the idiomatic way of writing it: fmap (+1) :: Tree Int -> Tree Int. What would be really neat is if we could elaborate the latter into former inside the compiler (out of sight from the developer).
Looking at the page you linked to, I can’t see any example where a function is defined over the serialised data without deserialising it first, i.e. what add1 does?
Deserialization is a bit of a misnomer, since FlatBuffers doesn’t deserialize the whole buffer when accessed. It just “decodes” the data that is requested, leaving all the other data untouched. It is up to the application to decide if the data is copied out or even read in the first place.
So the flatbuffers accessors are analogous to caseTree in the article, and the flatbuffers builders are analogous to the leaf and tree needy constructors.
Can flatbuffers represent recursive datatypes such as Tree? Can they modify the data in-place, rather than copying it when rebuilding? (To be fair, it’s not clear if the article does it in-place, but it seems plausible if the underlying array is mutable.)
I agree, this is what I thought as well.
In fact, this is one of the main tricks used in simdjson, see page 4, section 3, where they parse the (tree-like) nested JSON structure into a flat array called a tape, which is fast to navigate when retrieving values.
This is an okay stance to take & on the other end I can agree that CSS isn’t hard & don’t want to memorize a billion classname conventions, but what grinds my gears is when a tech lead or back-end team has mandated it on a front-end team that would prefer to not have that decision made for them—as can be said about most tools put on teams by folks not on those teams.
To me, if I want something that follows a system, the variables in Open Props covers what I need to have a consistent layer that a team can use—which is just CSS variables with a standardlized naming scheme other Open Prop modules can use. It is lighter weight, doesn’t need a compile step to be lean, & lets you structure your CSS or your HTML as you want without classname soup.
May not hard to write, but certainly it is hard to maintain. CSS makes it SO EASY to make a mess. In no time you’ll be facing selector specificity hell. If you have a team with juniors or just some backend folks trying to do some UI, that’s very common.
“But what about BEM?”. I like BEM! But, again it’s an extra step and another thing to learn (and you’re choosing not to deal with CSS specificiy to avoid its pitfalls).
IME, the BEM components I wrote were more effective and portable the smaller they were. I ended up with things like text text--small text--italic, which were basically in-house versions of Tailwind (before I knew what it was).
You can use utility classes & Open Prop names & still not use exclusively utility classes. No one has said Tailwind can’t be used for utility when needed, but in practice I see almost all name go away. There is nothing to select on that works for testing, or scraping, or filter lists.
Having a system is difficult since you have to make it stringly-typed one way or another, but that doesn’t discount the semantics or considering the balance needed. Often the UI & its architecture are low-priority or an afterthought since matching the design as quickly as possible tends to trump anything resembling maintainability & the same can happen in any code base if no standards are put in place & spaghetti is allowed to pass review.
It really is just the same old tired arguments on both sides tho really here. This isn’t the first time I have seen them & that post doesn’t really convince me given it has an agenda & some of the design choices seem intentionally obtuse without use of “modern” CSS from like the last 4–5 years.
but what grinds my gears is when a tech lead or back-end team has mandated it on a front-end team that would prefer to not have that decision made for them
Is this something that happened to you? Why would the back-end team decide on what technology the front-end team should use?
On multiple occasions have I seen a CTO, tech lead, or back-end team choose the stack for the front-end either before it was even started or purely based on a some proof-of-concept the back-end devs built & did not want the stack to change… just that it was entirely rewritten/refactored.
It raises the question that maybe the abstractions that were adopted by CSS are not the right ones in the long term, as other solutions are more team-friendly, easier to reason about.
Separation of presentation and content makes a lot of sense for a document format, and has been widely successful in that segment (think LaTex).
But for how the web is used today (think landing websites or webapps), the presentation is often part of the content. So the classic “Zen garden” model of CSS falls apart.
I’m in the same boat. I was appalled by it the first time I saw it.
But it fits my backend brain and makes building things so much easier. I like it in spite of myself. I’ve just never been able to bend my brain to automatically think in terms of the cascade, and reducing that to utilities makes it so much more workable for me, and lets me try things out faster. I’m excited about this release.
I am happy that you got a decent website up with Tailwind. I’m sad that you had a hard time with CSS and the conclusion you reached was that you were the one that sucked.
Whether you agree with the “politics” behind Lix or not, I am really happy that they got around to fixing a lot of long-standing issues with CppNix (Meson as a build system, lambda docs in repl, …).
The team behind it seems both highly motivated and competent, so congrats!
Generally, the so-called political discussions are coming from a place where talented people (who happen to be in marginalized groups) want to feel like they belong in a community, and that their efforts are rewarded. The people who only want to get in the way are very much the exceptions.
But in reality the hash is based off on the derivation file.
Normally, yes, but if you enable content-addressed derivations then the hash is calculated off the built artifacts.
However, this is actually not as important as the author seems to imply. Let’s say the hash is indeed based off the derivation file, does the author really understand what that means?
It means that whenever anything changes in any dependency (recursively), it forces Nix to rebuild the package, because the package is now depending on something that changed and which therefore could affect its build process.
I don’t know how other distros work nowadays, but when NixOS first appeared, the other mainstream distributions didn’t do this. You could even upgrade glibc (a base dependency of most packages) without package managers necessarily forcing you to rebuild (and reinstall) almost anything else. In NixOS however, if you do any change to glibc or say, gcc – even just adding a space character to a comment – Nix forces the entire distribution to be rebuilt.
This is also one of the main reasons why NixOS is (was?) considered to be more reproducible than your normal distro. It is (was?) much less prone to accidentally introducing reproducibility issues due to changes in dependencies or the build process in general.
We still do not know how to make that work, and any grandeur claims about having solved this problem should be met with a lot of scepticism.
Are you sure? As @dpc_pw mentioned, a deterministic VM like antithesis should definitely be able to guarantee 100% reproducible packages (barring any bugs, of course). That said, I don’t know what performance impact that would have. I wouldn’t be surprised if currently that would be prohibitively slow, depending on how big the distro is and how many resources it has. I would imagine a big package like Chromium would probably take weeks to rebuild if the deterministic VM is single-threaded like antithesis currently is.
This is also one of the main reasons why NixOS is (was?) considered to be more reproducible than your normal distro.
This is not really true? The focus on reproducible builds didn’t start until after the Debian efforts around 2016. You’ll find it hasn’t been mentioned in the context of NixOS until after this either. It’s cute that people try to retroactively write a trait into the distribution that never was a goal, but that is part of the reason I did write this.
I think you’re referring to the “reproducible builds” nomenclature and derived projects, which consisted in a systematic effort that indeed might have started with Debian.
However, Nix was already trying to address that issue even before that name and project became popular.
Wikipedia says the first NixOS release was Oct 2013, and if you look at a snapshot of NixOS and Nix from that era, you will find:
Nix describing itself as a “purely functional package manager”. What does purely functional mean? It means that the output is always the same given the same inputs.
NixOS using a description which includes “makes it easy to reproduce a configuration on another machine”.
Nix using the phrase “Nix tries very hard to ensure that Nix expressions are deterministic”. Which, as you probably know, if it’s deterministic then it’s also reproducible.
So yes, reproducibility was a goal of Nix and NixOS from the very beginning. And while it’s true that they didn’t start making more systematic efforts to ensure reproducible packages until after Debian had already started, it was designed from the start to address many of the reproducibility issues that existed at the time.
If I remember correctly, NixOS was also one of the first distros to use a package manager that built packages in a sandbox to improve reproducibility (although I admit I might be wrong on that).
So while Nix and NixOS didn’t start making more systematic efforts to ensure reproducible packages until after Debian had already started, it was designed from the start to address many of the reproducibility issues that existed at the time.
Walk the walk, talk the talk.
I don’t think statements on the webpage and no traceable effort to address the problems between 2013 and 2016 invalidates the criticism.
I don’t think statements on the webpage and no traceable effort to address the problems between 2013 and 2016 invalidates the criticism.
It’s not just the statements. Nix is still the closest thing to a mainstream, battle-tested purely functional package manager that you will ever find, even today. And it has always been so, from the very beginning (except for the mainstream and battle-tested parts, of course).
Nix’s impure mode isn’t very relevant here, since it’s all about Nix being able to access information from the environment in which it’s being run. Nix the language is always pure. An apples to apples comparison between Haskell’s IO monad and Nix is “import from derivation”, because in both cases, the outputs from an impure “runtime” can be used as input to a purely functional evaluation. Though, in Haskell, the IO action can interact with the real world in arbitrary ways, but a Nix derivation is built in a sandbox and is expected to behave deterministically.
You talked about what was “mentioned” as a way of evidencing what the “goals” of NixOS were from the start:
You’ll find it hasn’t been mentioned in the context of NixOS until after [Debian’s efforts]. It’s cute that people try to retroactively write a trait into the distribution that never was a goal, […].
Now you’re moving the goalposts. Judging by the statements given by wizeman, which you implied didn’t exist, that clearly was a goal of Nix from the start.
I don’t think I’m moving the goal post, because “Reproducible Builds” wasn’t mentioned before after the Debian initiatives. While wizeman believe this has always been implied by the fact it said “purely functional package manager” and “building a Nix expression twice should yield the same result”.
I should also be clear about I’m referring to what people are trying to solve in the project, if you try look back at nixpkgs and issues around this you’ll find there is one issue mentioning “deterministic builds” in 2016 and that is about it.
While wizeman believe this has always been implied by the fact it said “purely functional package manager” and “building a Nix expression twice should yield the same result”.
Are you saying that if building the same Nix package multiple times yields the same result, it’s not reproducible?
if you try look back at nixpkgs and issues around this you’ll find there is one issue mentioning “deterministic builds” in 2016 and that is about it.
You will find sentences such as “Of course, these properties—automatic derivations, side-by-side existence, rollbacks, identifiability and reproducibility—are all provided by Nix.”
The “reproducible builds” projects which you have been referring to, additionally, made even more efforts to improve reproducibility. But you can’t say this wasn’t something that Nix/NixOS was designed to solve, from the very beginning.
No wonder “Reproducible Builds” wasn’t mentioned before it was a thing. Nix has focused on reproducible builds (yes, lowercase) by definition, aiming to be a purely functional package manager, it’s focused on referential transparency (“deterministic builds”) and immutability (i.e. the read-only Nix store). “Reproducible Builds” is just a name for a thing Nix was clearly intended to do.
I don’t think I’m moving the goal post, because “Reproducible Builds” wasn’t mentioned before after the Debian initiatives.
But “deterministic builds”, which wizeman is saying is a subset of “reproducible builds”, was mentioned.
While wizeman believe this has always been implied by the fact it said “purely functional package manager” and “building a Nix expression twice should yield the same result”.
Wasn’t the latter the definition you appealed to also?:
A build is reproducible if given the same source code, build environment and build instructions, any party can recreate bit-by-bit identical copies of all specified artifacts.
So what’s the difference exactly between “building a Nix expression twice should yield the same result” and “given the same source code, build environment and build instructions, any party can recreate bit-by-bit identical copies of all specified artifacts”.
As for “purely functional package manager”, yes, that would also imply the same. If the inputs (in this case, source code, build environment, and build instructions) are the same, the output is the same. That’s what purely functional means.
Saying that Haskell — I assume you mean GHC — is not itself reproducible is a funny bit of irony, but it’s irrelevant. GHC may not itself by reproducible, but all pure Haskell functions give the same outputs from the same inputs. The apt comparison here would be between a pure Haskell function and a Nix expression, not between GHC and Nix itself.
If all Nix builds give the same outputs from the same inputs, then using the metric you use in the article, of judging the packages themselves, not judging how reproducible Nix itself is when built outside of Nix, then that goes some way to making the builds reproducible, and we can clearly see that the design was aimed at making it easy to make the builds reproducible from the start.
So we have several statements that tell us the goal from the start was a subset of reproducible builds, and we have a DSL which was deliberately built to
make build functions pure, and
(if I’m not wrong) make it easy or at least possible to specify inputs,
both of which work in tandem to make it easy to make the builds reproducible, we can see that not only was this a stated goal, work was also being done towards that end.
So what’s the difference exactly between “building a Nix expression twice should yield the same result” and “given the same source code, build environment and build instructions, any party can recreate bit-by-bit identical copies of all specified artifacts”.
It’s clear to me that “result” is the resulting path that is used in the nix store. This is input addressable and not necessarily content addressable so it can’t imply “bit-by-bit identical”.
As for “purely functional package manager”, yes, that would also imply the same. If the inputs (in this case, source code, build environment, and build instructions) are the same, the output is the same. That’s what purely functional means.
“Output” is abstract. In the context of NixOS this is the derivation files and the store path, not the content.
Saying that Haskell — I assume you mean GHC — is not itself reproducible is a funny bit of irony, but it’s irrelevant.
Of course, it’s a funny jab when people try to turn this into a mathematical discussion. It’s uninteresting.
It’s clear to me that “result” is the resulting path that is used in the nix store.
How so?
This is input addressable and not necessarily content addressable so it can’t imply “bit-by-bit identical”.
[…]
“Output” is abstract. In the context of NixOS this is the derivation files and the store path, not the content.
Well, making it input addressable means you don’t need to build two things to know if they’re the same.
If you have that guarantee of identical input resulting in identical output, then the same input hash will map to the same output hash anyway, but with the added advantage of not needing to actually do the work of building two things to know if they’re the same, so why not take advantage of the performance benefits? This is not a semantic difference.
Of course, it’s a funny jab when people try to turn this into a mathematical discussion. It’s uninteresting.
I’m genuinely not sure what you’re getting at here. I have an idea, but I’d rather just hear the clarification from you.
Most of these should be fairly uncontroversial. What rubs me the wrong way though is
Never Put { on the Same Line as Code
No, that’s not valid C++.
Uh, yes it is. There’s nothing in the standard that says otherwise. While it’s true that the isocpp guidelines mandate putting the open brace on a new line, there are plenty of big player style guides that mandate the opposite.
Lots of studies have been done and it is determined that the human brain needs the visual separation.
I’d like to see some of those studies proving that
void foo()
{
bar;
is superior to
void foo() {
bar;
or even
void foo() {
bar;
Even if those studies exist, I would argue that making efficient use of vertical space is even more important in a programming context.
Ultimately though, I think this is just a classic example of bikeshedding. Either way is fine, really. Claiming that one or the other is the “ultimate” way is a bit thick, in my opinion.
I’d like to see some of those studies proving that
Back when I was a young PhD student working on visualisation-related things, I did come across such studies, and now a couple of decades later I can’t find them, which I find incredibly frustrating.
The human (and, indeed, most animal) visual system has evolved to be really good at spotting bilateral symmetry. This is a huge evolutionary advantage because most things that might eat you have faces with bilateral symmetry. If you can see a thing with bilateral symmetry then there’s a good chance that a predator is looking at you. If you have a tiny brain with space for just a few reactions then triggering a freeze or flight reaction when you observe bilateral symmetry is a good survival strategy (and one that shows up in a lot of animals). When you have a bit more fidelity in your visual system, excluding members of the same species lets you build cooperative social structures (though isn’t always a win on its own because members of your own species are direct competitors for the exact resources you require).
This is why brackets have symmetric shapes: people are better at matching them because of the symmetry. If you used $ and € as brackets rather than ( and ), then matching pairs of them is visually much harder.
This ability degrades over distance in a few ways. Two brackets on the same line are easy to match. It gets worse if there’s line wrapping in the middle. If they are vertically aligned, it’s easier. You can use eye tracking to measure the time it takes to go from one bracket to the other.
The study that I recall (but can’t find!) used non-programmers and tracked how long it took their eyes to go from the open brace to the close brace. If anyone can help find it: it was sufficiently old that a big chunk of the paper was comparing { and } against begin and end because Pascal was still a dominant language (spoiler: begin and end do not have bilateral symmetry and so it takes longer for people to jump from begin to a matching end than from { to }, even if the braces have sub-optimal indentation). I can’t remember if they used eye tracking or relied on the user explicitly pointing to the end. It may have predated usable eye tracking.
The more that you can offload to the parts of the visual system that are always on, the more you spare cognitive resources you have for thinking about other things.
Note that the brace matching completely speedup goes away completely if the you can’t see both at the same time (you’re falling back to conscious tracking of the alignment) and is less efficient with smaller indent widths. False positives less of an evolutionary problem (you freeze or run away for no reason) than false negatives (you get eaten) and so if there’s a small indent your brain will try to match braces that are not quite at the same alignment.
This amused me when I started contributing to LLVM (in 2008, only a few years after reading the paper) because the LLVM coding conventions are almost the exact opposite of what people actually measuring these things recommended.
This seems quite plausible. But if the study focused on the ability to find the matching opening and closing braces, perhaps it did not take into account that the opening brace carries little useful information if you follow the common rule of always bracing even single statement blocks and use indentation. That is, usually you have no need to find the opening brace, so it is fine to stick it at the end.
You’re usually going in the other direction: you see the open brace and are looking for the close. Or you’re in the middle of a block and want to find the start. Consider the following two alternatives:
if (x) {
if (y) {
// Lots of code here
}
// Lots of code here
}
if (x)
{
if (y)
{
// Lots of code here
}
// Lots of code here
}
In both cases, you can start looking downwards to find the end and see a lone brace, but in the second you use your brain’s symmetry recognition to find the open brace, in the first you have to find things that are aligned, which is not something you’ve evolved to be able to do. If you visually scan up, you either need to find something at a lower indent level (harder when there are continued lines with extra indentation) in the first example, or the nearest brace in the second. Finding a brace on its own line is very easy for the human visual system. Then you’re scanning down to find the matching one and use symmetry in the second case.
Anecdotally, at this point in my career I have spend more time working on codebases (LLVM, FreeBSD,) that use some variant of the former style than the latter and, in spite of greater familiarity with the former style, I can still comprehend the structure of code faster if it’s written in the second style. It’s also interesting that people moving from the second style to the first complain about readability, whereas people moving in the opposite direction typically complain about wasted space and not about readability. I’ve yet to find someone who looks at code in the second format and says ‘I can’t see where the block starts and ends, this is hard to read’, but I’ve worked with several people who say variations of ‘there’s a lot of wasted vertical space here which makes it hard to follow the structure’ (to which my reaction is, if your code is too long to fit on a modern screen in the second style, that’s a different kind of code smell).
My objections to GNU style (braces indented one additional level) is purely cosmetic and personal preference, and not supported by any evidence.
Thanks, that does sound credible even without seeing the study. I still wonder if it really matters that much within the context of other available visual clues. Personally, when scanning for the limits of a block of code, I scan primarily based on indentation, that is, I look for a closing brace (or end, if I must) that is vertically aligned with the beginning of the block. Also, as you mentioned, distance matters, and putting the opening delimiter on a separate line does tend to increase vertical distance, especially on multi-branch conditions.
I am not sure that brackets-on-newline translates to better code quality or productivity though.
One could argue that by writing start brackets on the same line, you can gain one more line of screen estate.
Fitting more code on the screen could lead to better “big picture” comprehension of the program.
(I think I read a study on this, but I too cannot find it).
Very interesting (and plausible) stuff about symmetry recognition though.
One could argue that by writing start brackets on the same line, you can gain one more line of screen estate. Fitting more code on the screen could lead to better “big picture” comprehension of the program.
My experience in large codebases is the opposite. There are very few cases where there’s a big difference in what you can fit on the screen between the two cases, but if you have a code style that favours trying to cram more in then people seem to end up with huge functions. Pulling out self-contained bits into single-use always-inline functions generates the same code bug makes it easier to see the high-level flow and then zoom in, whereas cramming more code in vertically does not unless you’re right on the boundary of what fits on a screen (which might be more or less than the next reader can fit on their screen). It also tends to go along with coding styles that discourage large comments or whitespace to separate functionally distinct bits of the program, which also makes it harder to follow.
I think the endless disagreement about spacing is caused by text editors/terminals having different amounts of space between the lines, and more or less visually dense fonts.
If your editor has small line-height, then you’ll want to compensate with explicit line breaks. If your editor already has spacing between the lines, then you may be annoyed by less dense code.
Examples in the article even have horizontal spaces around ( and ), so the author may be bothered by the font without even realizing it.
there are plenty of big player style guides that mandate the opposite.
Probably written by people who learned the style from the K&R C book and have rationalized it to be “better.”
I personally came to the “brace on its own line” style pretty quickly when I learned C because for me it was easier to see with blocks of code. As far as vertical space goes, it’s been a long time since I used an actual terminal or even the 80x25 text screen of the IBM PC (35 years now?).
Claiming that one or the other is the “ultimate” way is a bit thick, in my opinion.
But people sure love to use very opinionated and non-configurable source code formaters these days (and in my opinion, never match my opinions on the matter).
In my next article, I am patching the sources of alejandra to use 4 spaces instead of 2. Alejandra is uncompromising, and so am I. Be sure to check the article out!
Nix should support tabs. This shouldn’t need to be an argument. braces for thrown tomatoes
Nix cannot support tabs (like it does for spaces).
In multiline ‘’-strings (single apostrophe), leading spaces are automatically removed.
This results in code looking much nicer because you (or a formatter) can indent the string as much as it wants without changing semantics.
This cannot be done with tabs, because that would mean breaking (changing semantics) of all existing multiline strings that have tabs in them.
I wonder whether a statistically significant difference could be found in preference for tabs vs spaces between developers whose experience heavily includes GitHub and other Web sites, where tab width isn’t easily customisable, and developers from a more old-fashioned background who are used to being able to customize tab width in their (locally running) editors.
GitLab & MS GitHub have a pretty obvious setting where you can in fact easily configure the tab width. But you are right that this should be an easy browser setting to override (which I do with userStyles.css).
Interestingly, like the post writer, I used Nix to override ocamlformat to pipe to GNU unexpand for an OCaml project (until I found topiary & got some suggested issue merged). But if the indentation were tabs, this would be a non-issue in the editor & it should not come as a surprise that different users have accessibility needs for their ability to read the code.
I’ve been saying elsewhere a lot lately that the whole virtual DOM thing is just fundamentally a bad idea… but fundamentally might be going a bit too far. If all state were included in the single source of truth, maybe it could be ok. I mean all though: text content, keyboard focus, caret location, current selection, scroll position, I’m probably forgetting some. HTML lets you define some, but not all, of these things, while the rest of it exists elsewhere.
You might be able to tie in two sources of state if each item had a unique identifier. But can you imagine being like return <div guid="......"> and keeping that consistent across partial tree rebuilds? I don’t think it is especially realistic.
But making a form of extended html + state management with the browser’s help … maybe. I still think it’d be easier said than done, but would surely simplify things relative to now.
(though personally im still all about that web 1.0 progressive enhancement. and yes iframes are still my friend :) )
Yes, I think criticisms of React are missing the point by focusing on problems of the heavy and fragile implementation. React itself is indeed problematic, but the idea that motivated it — stateless declarative UI, rather than mutation in place — is really important and a fundamental concept. It’s just so much easier to build complex UIs by making them functional/stateless. WebComponents completely ignored that need, and continued the old style of stateful mutable UI.
It’s almost unfair to say WC “ignored” the need so much as they predated React et al. and their attempt at making a declarative Shadow DOM was totally inadequate because it was speculative rather than paving a known desire line.
Reactive, pure ui is truly joy to develop for, but I don’t think it takes the right place on the (development speed) - (adaptability) - (user experience) matrix.
The vast majority of today’s reactive apps - in my opinion - are hell to use. Pure UI encourages developers to repaint the whole world on every state update - which results in buggy (I’ve seen many reactive apps crash and burn because of OOMs) and slow apps, making the UX frustrating.
Frameworks often encourage this kind of thinking in documentation, painting mutable state as some kind of inherent evil, an always incorrect solution.
On the contrary, on many apps, to achieve UX good enough for the average user, I’ve had to implement weird techniques to somehow retrofit mutable state on top of reactivity. I sadly don’t think Moore can alone get us out of this hole.
I am seeing the light at the end of the tunnel in the Svelte approach, where fancy high-level, magically-reactive components are utilized, but with a compiler instead of the runtime. This means every state transition can be compiled down to an efficient state machine, resulting in much better performance.
The availability to include optimization steps also contributes to that.
Overall, I think reactive development still has its place, and it is the least worst approach to UI development right now. Maybe the trend of stateless ui can motivate people smarter than me to finally tackle these problems.
The slow copy performance when it’s page-aligned is really something. Wonder if this is somewhere there was a CPU bug (like, page-aligned copies could corrupt or leak data) and the slow performance we’re seeing is the end result of microcode adding a workaround or mitigation.
I suspect it’s someone being clever in microcode. The flow I imagine is this:
If something is page aligned, we know that either the first load / store will trap or none will for the remainder of the page, so let’s do a special case for that which is fast.
The common case is less-aligned data, let’s aggressively optimise that.
Oh, oops, we forgot to apply the optimisations in the fast-path case.
You might be right though. One of the Specre variants (I think on Intel) involved the first cache line in a page being special. It’s possible that AMD has some mitigations for a similar vulnerability in their rep movsb microcode.
One of the Specre variants (I think on Intel) involved the first cache line in a page being special. It’s possible that AMD has some mitigations for a similar vulnerability in their rep movsb microcode.
If the non-aligned access is still fast, I wonder if the mitigation is missed…
Sort of, it can be slower if you’re not careful, but it doesn’t have to be. These two approaches conceptually do the exact same thing and both run at ~13.5 GiB/s on my machine. I opted for .fold(), which takes an initial value, vs .reduce() which returns an Option::None if the input is empty:
input.bytes().map(|b| (b & 1) as i64).sum::<i64>();
input.bytes().fold(0, |s, b| s + (b & 1) as i64);
But consider these faster implementations, which accumulate big vectors of u8 into a single u8 before accumulating those into an i64. Even with fold, there’s still the sum at the end. Both of these pull ~85-86 GiB/s on my machine:
pub fn opt6_chunk_exact_count(input: &str) -> i64 {
let iter = input.as_bytes().chunks_exact(192);
let rest = iter.remainder();
let mut n_s = iter
.map(|chunk| chunk.iter().map(|&b| b & 1).sum::<u8>() as i64)
.sum::<i64>();
n_s += rest.iter().map(|&b| b & 1).sum::<u8>() as i64;
(2 * n_s) - input.len() as i64
}
.map(|chunk| chunk.iter().fold(0, |s, b| s + (b & 1)) as i64)
.sum::<i64>();
n_s += rest.iter().fold(0, |s, b| s + (b & 1)) as i64;
I did not come up with this approach, it was contributed to the repo after the blog was published.
The issue is, the exact fold you have to do is finicky. You have to make sure your inner vectorized loop accumulates to u8 and only sums as i64 late as possible to get the best performance. This only gets ~63 GiB/s, ~26% slower:
.map(|chunk| chunk.iter().fold(0, |s, b| s + (b & 1) as i64))
.sum::<i64>();
n_s += rest.iter().fold(0, |s, b| s + (b & 1) as i64);
Can you spot the difference? Only 1 paren has moved a bit, which changes the type of the inner accumulator from u8 to i64. Here is the key difference with type annotations:
.fold(0, |s: u8 , b: u8| s + (b & 1)) as i64)
.fold(0, |s: i64, b: u8| s + (b & 1) as i64))
Accumulating into an i64 directly in the inner loop makes the compiler sad. To me, writing .map().sum::<u8>() makes the intended vectorization and intended inner accumulator type more explicit, even compared to the type-annotated folds. So technically no, fold / reduce is not slower, it’s just easier to get wrong in this case.
This stuff is moving incredibly fast. GPT-4 already hallucinates far less than GPT-3.5. You can still come up with situations where it will, but they are much more rare.
Isn’t this what the data-oriented design people do?
Yeah, that’s cool, but at what cost? Compare
add1in the post vs the idiomatic way of writing it:fmap (+1) :: Tree Int -> Tree Int. What would be really neat is if we could elaborate the latter into former inside the compiler (out of sight from the developer).I think a closer point of comparison is flatbuffers
Looking at the page you linked to, I can’t see any example where a function is defined over the serialised data without deserialising it first, i.e. what
add1does?The tutorial says,
So the flatbuffers accessors are analogous to
caseTreein the article, and the flatbuffers builders are analogous to theleafandtreeneedy constructors.Can flatbuffers represent recursive datatypes such as
Tree? Can they modify the data in-place, rather than copying it when rebuilding? (To be fair, it’s not clear if the article does it in-place, but it seems plausible if the underlying array is mutable.)Compare with DoD, https://lobste.rs/s/8bhptf/data_oriented_design_revisited_type , where they do have recursive types and do it in-place.
I agree, this is what I thought as well. In fact, this is one of the main tricks used in simdjson, see page 4, section 3, where they parse the (tree-like) nested JSON structure into a flat array called a tape, which is fast to navigate when retrieving values.
People are rightly hard on tailwind.
And yet… for someone who sucks at front end responsive design I can’t deny it didn’t take me long to get a decent website up and running.
IME, the critics of Tailwind CSS are often CSS experts. So to them, “CSS isn’t hard”. The Tailwind abstractions just seem an extra step to them.
To me, though, they’re very useful and portable.
This is an okay stance to take & on the other end I can agree that CSS isn’t hard & don’t want to memorize a billion classname conventions, but what grinds my gears is when a tech lead or back-end team has mandated it on a front-end team that would prefer to not have that decision made for them—as can be said about most tools put on teams by folks not on those teams.
To me, if I want something that follows a system, the variables in Open Props covers what I need to have a consistent layer that a team can use—which is just CSS variables with a standardlized naming scheme other Open Prop modules can use. It is lighter weight, doesn’t need a compile step to be lean, & lets you structure your CSS or your HTML as you want without classname soup.
May not hard to write, but certainly it is hard to maintain. CSS makes it SO EASY to make a mess. In no time you’ll be facing selector specificity hell. If you have a team with juniors or just some backend folks trying to do some UI, that’s very common.
“But what about BEM?”. I like BEM! But, again it’s an extra step and another thing to learn (and you’re choosing not to deal with CSS specificiy to avoid its pitfalls).
IME, the BEM components I wrote were more effective and portable the smaller they were. I ended up with things like
text text--small text--italic, which were basically in-house versions of Tailwind (before I knew what it was).So, to paraphrase Adam, I rather have static CSS and change HTML than the reverse.
You can use utility classes & Open Prop names & still not use exclusively utility classes. No one has said Tailwind can’t be used for utility when needed, but in practice I see almost all name go away. There is nothing to select on that works for testing, or scraping, or filter lists.
Having a system is difficult since you have to make it stringly-typed one way or another, but that doesn’t discount the semantics or considering the balance needed. Often the UI & its architecture are low-priority or an afterthought since matching the design as quickly as possible tends to trump anything resembling maintainability & the same can happen in any code base if no standards are put in place & spaghetti is allowed to pass review.
It really is just the same old tired arguments on both sides tho really here. This isn’t the first time I have seen them & that post doesn’t really convince me given it has an agenda & some of the design choices seem intentionally obtuse without use of “modern” CSS from like the last 4–5 years.
Is this something that happened to you? Why would the back-end team decide on what technology the front-end team should use?
On multiple occasions have I seen a CTO, tech lead, or back-end team choose the stack for the front-end either before it was even started or purely based on a some proof-of-concept the back-end devs built & did not want the stack to change… just that it was entirely rewritten/refactored.
It raises the question that maybe the abstractions that were adopted by CSS are not the right ones in the long term, as other solutions are more team-friendly, easier to reason about.
I find the original tailwind blog post really enlightening: https://adamwathan.me/css-utility-classes-and-separation-of-concerns/
Separation of presentation and content makes a lot of sense for a document format, and has been widely successful in that segment (think LaTex). But for how the web is used today (think landing websites or webapps), the presentation is often part of the content. So the classic “Zen garden” model of CSS falls apart.
I’m in the same boat. I was appalled by it the first time I saw it.
But it fits my backend brain and makes building things so much easier. I like it in spite of myself. I’ve just never been able to bend my brain to automatically think in terms of the cascade, and reducing that to utilities makes it so much more workable for me, and lets me try things out faster. I’m excited about this release.
I am happy that you got a decent website up with Tailwind. I’m sad that you had a hard time with CSS and the conclusion you reached was that you were the one that sucked.
I really can’t be bothered to learn CSS or deal with “web standards”.
Whether you agree with the “politics” behind Lix or not, I am really happy that they got around to fixing a lot of long-standing issues with CppNix (Meson as a build system, lambda docs in repl, …).
The team behind it seems both highly motivated and competent, so congrats!
Generally, the so-called political discussions are coming from a place where talented people (who happen to be in marginalized groups) want to feel like they belong in a community, and that their efforts are rewarded. The people who only want to get in the way are very much the exceptions.
Normally, yes, but if you enable content-addressed derivations then the hash is calculated off the built artifacts.
However, this is actually not as important as the author seems to imply. Let’s say the hash is indeed based off the derivation file, does the author really understand what that means?
It means that whenever anything changes in any dependency (recursively), it forces Nix to rebuild the package, because the package is now depending on something that changed and which therefore could affect its build process.
I don’t know how other distros work nowadays, but when NixOS first appeared, the other mainstream distributions didn’t do this. You could even upgrade glibc (a base dependency of most packages) without package managers necessarily forcing you to rebuild (and reinstall) almost anything else. In NixOS however, if you do any change to glibc or say, gcc – even just adding a space character to a comment – Nix forces the entire distribution to be rebuilt.
This is also one of the main reasons why NixOS is (was?) considered to be more reproducible than your normal distro. It is (was?) much less prone to accidentally introducing reproducibility issues due to changes in dependencies or the build process in general.
Are you sure? As @dpc_pw mentioned, a deterministic VM like antithesis should definitely be able to guarantee 100% reproducible packages (barring any bugs, of course). That said, I don’t know what performance impact that would have. I wouldn’t be surprised if currently that would be prohibitively slow, depending on how big the distro is and how many resources it has. I would imagine a big package like Chromium would probably take weeks to rebuild if the deterministic VM is single-threaded like antithesis currently is.
This is not really true? The focus on reproducible builds didn’t start until after the Debian efforts around 2016. You’ll find it hasn’t been mentioned in the context of NixOS until after this either. It’s cute that people try to retroactively write a trait into the distribution that never was a goal, but that is part of the reason I did write this.
I think you’re referring to the “reproducible builds” nomenclature and derived projects, which consisted in a systematic effort that indeed might have started with Debian.
However, Nix was already trying to address that issue even before that name and project became popular.
Wikipedia says the first NixOS release was Oct 2013, and if you look at a snapshot of NixOS and Nix from that era, you will find:
So yes, reproducibility was a goal of Nix and NixOS from the very beginning. And while it’s true that they didn’t start making more systematic efforts to ensure reproducible packages until after Debian had already started, it was designed from the start to address many of the reproducibility issues that existed at the time.
If I remember correctly, NixOS was also one of the first distros to use a package manager that built packages in a sandbox to improve reproducibility (although I admit I might be wrong on that).
Walk the walk, talk the talk.
I don’t think statements on the webpage and no traceable effort to address the problems between 2013 and 2016 invalidates the criticism.
It’s not just the statements. Nix is still the closest thing to a mainstream, battle-tested purely functional package manager that you will ever find, even today. And it has always been so, from the very beginning (except for the mainstream and battle-tested parts, of course).
This is about Reproducible Builds though.
And what do you think a purely functional package manager is designed to address that other package managers aren’t?
What is a pure function to you?
Considering the purely functional language of Haskell is the least reproducible one, I don’t know?
This is some weird bait.
I agree. With all due respect, I think you don’t know…
Haskell has Monads to cope with impurity. Nix doesn’t unless you run in –impure mode.
Nix’s impure mode isn’t very relevant here, since it’s all about Nix being able to access information from the environment in which it’s being run. Nix the language is always pure. An apples to apples comparison between Haskell’s IO monad and Nix is “import from derivation”, because in both cases, the outputs from an impure “runtime” can be used as input to a purely functional evaluation. Though, in Haskell, the IO action can interact with the real world in arbitrary ways, but a Nix derivation is built in a sandbox and is expected to behave deterministically.
You talked about what was “mentioned” as a way of evidencing what the “goals” of NixOS were from the start:
Now you’re moving the goalposts. Judging by the statements given by wizeman, which you implied didn’t exist, that clearly was a goal of Nix from the start.
I don’t think I’m moving the goal post, because “Reproducible Builds” wasn’t mentioned before after the Debian initiatives. While wizeman believe this has always been implied by the fact it said “purely functional package manager” and “building a Nix expression twice should yield the same result”.
I should also be clear about I’m referring to what people are trying to solve in the project, if you try look back at
nixpkgsand issues around this you’ll find there is one issue mentioning “deterministic builds” in 2016 and that is about it.Are you saying that if building the same Nix package multiple times yields the same result, it’s not reproducible?
You can go and read Eelco Dolstra’s PhD thesis in which he introduced Nix to the world, which was defended in 2006, and you will find 20 matches for the regex “reproduc*” and 7 others for the word “deterministic”.
You will find sentences such as “Of course, these properties—automatic derivations, side-by-side existence, rollbacks, identifiability and reproducibility—are all provided by Nix.”
The “reproducible builds” projects which you have been referring to, additionally, made even more efforts to improve reproducibility. But you can’t say this wasn’t something that Nix/NixOS was designed to solve, from the very beginning.
No wonder “Reproducible Builds” wasn’t mentioned before it was a thing. Nix has focused on reproducible builds (yes, lowercase) by definition, aiming to be a purely functional package manager, it’s focused on referential transparency (“deterministic builds”) and immutability (i.e. the read-only Nix store). “Reproducible Builds” is just a name for a thing Nix was clearly intended to do.
But “deterministic builds”, which wizeman is saying is a subset of “reproducible builds”, was mentioned.
Wasn’t the latter the definition you appealed to also?:
So what’s the difference exactly between “building a Nix expression twice should yield the same result” and “given the same source code, build environment and build instructions, any party can recreate bit-by-bit identical copies of all specified artifacts”.
As for “purely functional package manager”, yes, that would also imply the same. If the inputs (in this case, source code, build environment, and build instructions) are the same, the output is the same. That’s what purely functional means.
Saying that Haskell — I assume you mean GHC — is not itself reproducible is a funny bit of irony, but it’s irrelevant. GHC may not itself by reproducible, but all pure Haskell functions give the same outputs from the same inputs. The apt comparison here would be between a pure Haskell function and a Nix expression, not between GHC and Nix itself.
If all Nix builds give the same outputs from the same inputs, then using the metric you use in the article, of judging the packages themselves, not judging how reproducible Nix itself is when built outside of Nix, then that goes some way to making the builds reproducible, and we can clearly see that the design was aimed at making it easy to make the builds reproducible from the start.
So we have several statements that tell us the goal from the start was a subset of reproducible builds, and we have a DSL which was deliberately built to
both of which work in tandem to make it easy to make the builds reproducible, we can see that not only was this a stated goal, work was also being done towards that end.
It’s clear to me that “result” is the resulting path that is used in the nix store. This is input addressable and not necessarily content addressable so it can’t imply “bit-by-bit identical”.
“Output” is abstract. In the context of NixOS this is the derivation files and the store path, not the content.
Of course, it’s a funny jab when people try to turn this into a mathematical discussion. It’s uninteresting.
How so?
Well, making it input addressable means you don’t need to build two things to know if they’re the same.
If you have that guarantee of identical input resulting in identical output, then the same input hash will map to the same output hash anyway, but with the added advantage of not needing to actually do the work of building two things to know if they’re the same, so why not take advantage of the performance benefits? This is not a semantic difference.
I’m genuinely not sure what you’re getting at here. I have an idea, but I’d rather just hear the clarification from you.
Most of these should be fairly uncontroversial. What rubs me the wrong way though is
Uh, yes it is. There’s nothing in the standard that says otherwise. While it’s true that the isocpp guidelines mandate putting the open brace on a new line, there are plenty of big player style guides that mandate the opposite.
I’d like to see some of those studies proving that
is superior to
or even
Even if those studies exist, I would argue that making efficient use of vertical space is even more important in a programming context.
Ultimately though, I think this is just a classic example of bikeshedding. Either way is fine, really. Claiming that one or the other is the “ultimate” way is a bit thick, in my opinion.
Back when I was a young PhD student working on visualisation-related things, I did come across such studies, and now a couple of decades later I can’t find them, which I find incredibly frustrating.
The human (and, indeed, most animal) visual system has evolved to be really good at spotting bilateral symmetry. This is a huge evolutionary advantage because most things that might eat you have faces with bilateral symmetry. If you can see a thing with bilateral symmetry then there’s a good chance that a predator is looking at you. If you have a tiny brain with space for just a few reactions then triggering a freeze or flight reaction when you observe bilateral symmetry is a good survival strategy (and one that shows up in a lot of animals). When you have a bit more fidelity in your visual system, excluding members of the same species lets you build cooperative social structures (though isn’t always a win on its own because members of your own species are direct competitors for the exact resources you require).
This is why brackets have symmetric shapes: people are better at matching them because of the symmetry. If you used $ and € as brackets rather than ( and ), then matching pairs of them is visually much harder.
This ability degrades over distance in a few ways. Two brackets on the same line are easy to match. It gets worse if there’s line wrapping in the middle. If they are vertically aligned, it’s easier. You can use eye tracking to measure the time it takes to go from one bracket to the other.
The study that I recall (but can’t find!) used non-programmers and tracked how long it took their eyes to go from the open brace to the close brace. If anyone can help find it: it was sufficiently old that a big chunk of the paper was comparing
{and}againstbeginandendbecause Pascal was still a dominant language (spoiler:beginandenddo not have bilateral symmetry and so it takes longer for people to jump frombeginto a matchingendthan from{to}, even if the braces have sub-optimal indentation). I can’t remember if they used eye tracking or relied on the user explicitly pointing to the end. It may have predated usable eye tracking.The more that you can offload to the parts of the visual system that are always on, the more you spare cognitive resources you have for thinking about other things.
Note that the brace matching completely speedup goes away completely if the you can’t see both at the same time (you’re falling back to conscious tracking of the alignment) and is less efficient with smaller indent widths. False positives less of an evolutionary problem (you freeze or run away for no reason) than false negatives (you get eaten) and so if there’s a small indent your brain will try to match braces that are not quite at the same alignment.
This amused me when I started contributing to LLVM (in 2008, only a few years after reading the paper) because the LLVM coding conventions are almost the exact opposite of what people actually measuring these things recommended.
This seems quite plausible. But if the study focused on the ability to find the matching opening and closing braces, perhaps it did not take into account that the opening brace carries little useful information if you follow the common rule of always bracing even single statement blocks and use indentation. That is, usually you have no need to find the opening brace, so it is fine to stick it at the end.
You’re usually going in the other direction: you see the open brace and are looking for the close. Or you’re in the middle of a block and want to find the start. Consider the following two alternatives:
In both cases, you can start looking downwards to find the end and see a lone brace, but in the second you use your brain’s symmetry recognition to find the open brace, in the first you have to find things that are aligned, which is not something you’ve evolved to be able to do. If you visually scan up, you either need to find something at a lower indent level (harder when there are continued lines with extra indentation) in the first example, or the nearest brace in the second. Finding a brace on its own line is very easy for the human visual system. Then you’re scanning down to find the matching one and use symmetry in the second case.
Anecdotally, at this point in my career I have spend more time working on codebases (LLVM, FreeBSD,) that use some variant of the former style than the latter and, in spite of greater familiarity with the former style, I can still comprehend the structure of code faster if it’s written in the second style. It’s also interesting that people moving from the second style to the first complain about readability, whereas people moving in the opposite direction typically complain about wasted space and not about readability. I’ve yet to find someone who looks at code in the second format and says ‘I can’t see where the block starts and ends, this is hard to read’, but I’ve worked with several people who say variations of ‘there’s a lot of wasted vertical space here which makes it hard to follow the structure’ (to which my reaction is, if your code is too long to fit on a modern screen in the second style, that’s a different kind of code smell).
My objections to GNU style (braces indented one additional level) is purely cosmetic and personal preference, and not supported by any evidence.
Thanks, that does sound credible even without seeing the study. I still wonder if it really matters that much within the context of other available visual clues. Personally, when scanning for the limits of a block of code, I scan primarily based on indentation, that is, I look for a closing brace (or
end, if I must) that is vertically aligned with the beginning of the block. Also, as you mentioned, distance matters, and putting the opening delimiter on a separate line does tend to increase vertical distance, especially on multi-branch conditions.I am not sure that brackets-on-newline translates to better code quality or productivity though.
One could argue that by writing start brackets on the same line, you can gain one more line of screen estate. Fitting more code on the screen could lead to better “big picture” comprehension of the program.
(I think I read a study on this, but I too cannot find it).
Very interesting (and plausible) stuff about symmetry recognition though.
My experience in large codebases is the opposite. There are very few cases where there’s a big difference in what you can fit on the screen between the two cases, but if you have a code style that favours trying to cram more in then people seem to end up with huge functions. Pulling out self-contained bits into single-use always-inline functions generates the same code bug makes it easier to see the high-level flow and then zoom in, whereas cramming more code in vertically does not unless you’re right on the boundary of what fits on a screen (which might be more or less than the next reader can fit on their screen). It also tends to go along with coding styles that discourage large comments or whitespace to separate functionally distinct bits of the program, which also makes it harder to follow.
I think the endless disagreement about spacing is caused by text editors/terminals having different amounts of space between the lines, and more or less visually dense fonts.
If your editor has small line-height, then you’ll want to compensate with explicit line breaks. If your editor already has spacing between the lines, then you may be annoyed by less dense code.
Examples in the article even have horizontal spaces around
(and), so the author may be bothered by the font without even realizing it.don’t forget the venerable:
Probably written by people who learned the style from the K&R C book and have rationalized it to be “better.”
I personally came to the “brace on its own line” style pretty quickly when I learned C because for me it was easier to see with blocks of code. As far as vertical space goes, it’s been a long time since I used an actual terminal or even the 80x25 text screen of the IBM PC (35 years now?).
But people sure love to use very opinionated and non-configurable source code formaters these days (and in my opinion, never match my opinions on the matter).
Nix should support tabs. This shouldn’t need to be an argument. braces for thrown tomatoes
Nix cannot support tabs (like it does for spaces).
In multiline ‘’-strings (single apostrophe), leading spaces are automatically removed. This results in code looking much nicer because you (or a formatter) can indent the string as much as it wants without changing semantics.
This cannot be done with tabs, because that would mean breaking (changing semantics) of all existing multiline strings that have tabs in them.
Then break multiline strings for accessibility 🤷 Currently it’s broken in the other direction where you can’t put a tab in a spaced multiline string.
I wonder whether a statistically significant difference could be found in preference for tabs vs spaces between developers whose experience heavily includes GitHub and other Web sites, where tab width isn’t easily customisable, and developers from a more old-fashioned background who are used to being able to customize tab width in their (locally running) editors.
GitLab & MS GitHub have a pretty obvious setting where you can in fact easily configure the tab width. But you are right that this should be an easy browser setting to override (which I do with userStyles.css).
Interestingly, like the post writer, I used Nix to override
ocamlformatto pipe to GNUunexpandfor an OCaml project (until I foundtopiary& got some suggested issue merged). But if the indentation were tabs, this would be a non-issue in the editor & it should not come as a surprise that different users have accessibility needs for their ability to read the code.I’ve been saying elsewhere a lot lately that the whole virtual DOM thing is just fundamentally a bad idea… but fundamentally might be going a bit too far. If all state were included in the single source of truth, maybe it could be ok. I mean all though: text content, keyboard focus, caret location, current selection, scroll position, I’m probably forgetting some. HTML lets you define some, but not all, of these things, while the rest of it exists elsewhere.
You might be able to tie in two sources of state if each item had a unique identifier. But can you imagine being like
return <div guid="......">and keeping that consistent across partial tree rebuilds? I don’t think it is especially realistic.But making a form of extended html + state management with the browser’s help … maybe. I still think it’d be easier said than done, but would surely simplify things relative to now.
(though personally im still all about that web 1.0 progressive enhancement. and yes iframes are still my friend :) )
Yes, I think criticisms of React are missing the point by focusing on problems of the heavy and fragile implementation. React itself is indeed problematic, but the idea that motivated it — stateless declarative UI, rather than mutation in place — is really important and a fundamental concept. It’s just so much easier to build complex UIs by making them functional/stateless. WebComponents completely ignored that need, and continued the old style of stateful mutable UI.
It’s almost unfair to say WC “ignored” the need so much as they predated React et al. and their attempt at making a declarative Shadow DOM was totally inadequate because it was speculative rather than paving a known desire line.
Reactive, pure ui is truly joy to develop for, but I don’t think it takes the right place on the (development speed) - (adaptability) - (user experience) matrix.
The vast majority of today’s reactive apps - in my opinion - are hell to use. Pure UI encourages developers to repaint the whole world on every state update - which results in buggy (I’ve seen many reactive apps crash and burn because of OOMs) and slow apps, making the UX frustrating.
Frameworks often encourage this kind of thinking in documentation, painting mutable state as some kind of inherent evil, an always incorrect solution. On the contrary, on many apps, to achieve UX good enough for the average user, I’ve had to implement weird techniques to somehow retrofit mutable state on top of reactivity. I sadly don’t think Moore can alone get us out of this hole.
I am seeing the light at the end of the tunnel in the Svelte approach, where fancy high-level, magically-reactive components are utilized, but with a compiler instead of the runtime. This means every state transition can be compiled down to an efficient state machine, resulting in much better performance. The availability to include optimization steps also contributes to that.
Overall, I think reactive development still has its place, and it is the least worst approach to UI development right now. Maybe the trend of stateless ui can motivate people smarter than me to finally tackle these problems.
Mild spoilers if you haven’t read it :)
The slow copy performance when it’s page-aligned is really something. Wonder if this is somewhere there was a CPU bug (like, page-aligned copies could corrupt or leak data) and the slow performance we’re seeing is the end result of microcode adding a workaround or mitigation.
Extra ironic because conventional wisdom is to align stuff for best performance.
and for less clang warnings about UB with unaligned access
I suspect it’s someone being clever in microcode. The flow I imagine is this:
You might be right though. One of the Specre variants (I think on Intel) involved the first cache line in a page being special. It’s possible that AMD has some mitigations for a similar vulnerability in their rep movsb microcode.
If the non-aligned access is still fast, I wonder if the mitigation is missed…
I went to the authors website to see if the blog had RSS.
It doesn’t look like there are any other discoverable articles, which is a shame. I’d definitely subscribe, very interesting article
Can someone explain why the author used map().sum() instead of reduce? would that be slower? If yes, why?
Sort of, it can be slower if you’re not careful, but it doesn’t have to be. These two approaches conceptually do the exact same thing and both run at ~13.5 GiB/s on my machine. I opted for
.fold(), which takes an initial value, vs.reduce()which returns anOption::Noneif the input is empty:But consider these faster implementations, which accumulate big vectors of
u8into a singleu8before accumulating those into ani64. Even with fold, there’s still the sum at the end. Both of these pull ~85-86 GiB/s on my machine:I did not come up with this approach, it was contributed to the repo after the blog was published.
The issue is, the exact fold you have to do is finicky. You have to make sure your inner vectorized loop accumulates to
u8and only sums asi64late as possible to get the best performance. This only gets ~63 GiB/s, ~26% slower:Can you spot the difference? Only 1 paren has moved a bit, which changes the type of the inner accumulator from
u8toi64. Here is the key difference with type annotations:Accumulating into an
i64directly in the inner loop makes the compiler sad. To me, writing.map().sum::<u8>()makes the intended vectorization and intended inner accumulator type more explicit, even compared to the type-annotated folds. So technically no, fold / reduce is not slower, it’s just easier to get wrong in this case.This stuff is moving incredibly fast. GPT-4 already hallucinates far less than GPT-3.5. You can still come up with situations where it will, but they are much more rare.
I am interested. do you have sources on less hallucination?
I guess passing exams better would be a proxy.
“far less” here means between 10% and 40% reduction depending on how they count https://openai.com/research/gpt-4#limitations