12 ms·
Every Byte Matters
- maoliofc 4mo ago[flagged]
- ssiddharth 4mo agoSlight tangent, but every ms, μs, and ns counts too. We've gotten awfully carefree with response times and wasted compute cycles.
- coldcity_again 4mo agoI love to see stuff like this. And an active Vectrex gamedev and PC/Amiga sizecoder I strongly agree with the sentiment!
- forinti 4mo agoSo if you need speed, you just have to swallow your OO programmer's pride and put your data in arrays.
- theandrewbailey 4mo agoMaybe someone can write an OO language where arrays of structs are automatically stored as structs of arrays. mild /s
- Mizza 4mo agoAre you talking about Zig's MultiArrayList?
- alex7o 4mo agoHe is talking about jai the programing language from Jonathan Blow, which is quite cool but there is no way to access it.
- fp64 4mo agoOdin has some helpers, was one of the more interesting features I found, but never tried. Not sure if you want to consider Odin OO, but well https://odin-lang.org/docs/overview/#soa-struct-arrays https://odin-lang.org/docs/overview/#soa-struct-arrays
- the__alchemist 4mo agoOdin is heavily inspired by the lang he or she is referring to!
- fp64 4mo agoA sibling comment also mentioned Jai. Not sure what I am missing that the original post was explicitly referring to Jai, some inside joke maybe? I am sorry, I only know Odin. Jai is this cult on reddit/discord, right? You get access if you socialize enough or something? Not my thing. Not for a language.
- theandrewbailey 4mo ago(original poster here) I was just throwing out an idea. I had no idea there were already implementations! Because, to my knowledge, conventional popular languages like C/C++/C#/Java/JS/Python don't do that, and automatically doing that (under certain conditions) feels like an easy performance win.
- jevndev 4mo agoFor what it’s worth, a common example of the capabilities of c++26 reflection is exactly this use case. I can’t remember where I first saw it, but this article [0] showcases the technique pretty well. It’s opt-in so not the compiler optimization that you’re imagining but still neat that it’s possible [0] https://brevzin.github.io/c++/2025/05/02/soa/ https://brevzin.github.io/c++/2025/05/02/soa/
- the__alchemist 4mo agoAh. So, the context (Which I read too far into evidently): 1: One of Jai's initial primary marketing points was to address exactly this: SoA performance with AoS ergonomics. 2: Odin is (or was initially) inspired by Jai.
- tlb 4mo agoThere's a package to do this in Julia: https://juliaarrays.github.io/StructArrays.jl/stable/ https://juliaarrays.github.io/StructArrays.jl/stable/
- gryn 4mo agosomething like this https://crates.io/crates/columnar https://crates.io/crates/columnar ?
- bob1029 4mo agoAnd avoid moving said data between physical threads as much as possible. Most of the bottlenecks I see are not due to the organization of data. Unnecessary communication of data is the #1 offender.
- burnt-resistor 4mo agoWorking set and algorithm diagonalization (work independence) FTW. Immutable data structures and copying often helps to avoid cache invalidation penalties.
- jayd16 4mo agoIf you have hot loops with millions of iterations at a time, structure your code accordingly. Its not anti-OO to choose the right data structure for the job.
- kerblang 4mo ago... IF that's your main performance problem. I already know I'm dealing with huge perf issues caused by ORM & lazy-load semantics. I/O abuse is usually going to be so, so much worse than memory/cache issues. Java is mainly used for business information systems, where I/O is king. Plain vanilla memory abuse is also a big one. But my main problem is a mgmt convinced the magic wand of AI will make all sorts of problems dissapear, and it's going to take 5 years for them to realize nope. It's still fun to learn about cache optimization though, esp. when someone makes it reasonably digestible like this. And maybe it also helps people to recognize that OOP is not some great over-arching zen truth of truths.
- chadgpt3 4mo agoIf you had the right language you could use AoS syntax with SoA implementation. I heard Jai was going to have this feature?
- noelwelsh 4mo agoThe JVM is currently pretty bad for memory allocation. Every object (i.e. not a primitive) has a header that IIRC is 12 bytes. But there is good news in JVM land: this will be reduced to 8 bytes in the next JVM release, and Project Valhalla will give the tools to do away with headers entirely in some cases. Project Valhalla also has tools to manage off-heap memory, which is important in many cases. The JVM is an odd place where it requires too much heap to compete with the AOT compiled languages, but its startup time is too slow compared to interpreted languages. I think these enhancements are essential to keep the platform relevant.
- kakacik 4mo agoMost of real world use of Java platform has next to 0 concerns like those. Some more niche use case may benefit, good, but overall success map isn't changing anytime soon. Reasons for its long term success lie elsewhere.
- FartyMcFarter 4mo agoAndroid Java apps' memory consumption is definitely a relevant concern.
- re-thc 4mo agoNot true. Lots of large Java deployments with millions to billions in cloud spend. The Java part of it isn’t 0. Memory isn’t free. CPU isn’t free.
- 4mo ago
- yas_hmaheshwari 4mo agoOut of course: I had thought about reading an article about Iran war or some geo political news when I read fzakaria :-)
- pron 4mo ago> The cost of each new field is rarely considered Most developers, in Java and in most other languages, do not consider the cost of every field, but I can tell you that people who need micro-optimisations certainly do care, and in Java's standard library, a layout is very much a concern (except, as always, you want to optimise what really matters; there's no point in optimising something that is unlikely to be a hot spot in a real program). Sometimes, though, you want to intentionally spread out the layout to avoid cache line sharing when concurrency is involved. You will find such examples in the standard library, too.
- re-thc 4mo ago> Most developers, in Java and in most other languages, do not consider the cost of every field Are you saying most developers are bad? It’s the equivalent of most employees don’t consider the cost of every action to the employer and is how company spend blows up.
- nathanielks 4mo agoIf the previous commenter won't say that, I will
- Retr0id 4mo agoMost likely they just have other priorities. A lot of code is not at all performance-sensitive, or is bottlenecked by some other factor.
- pron 4mo agoI'm saying that most developers aren't writing code where layout is a primary contributor to the program's performance. Even in performance-sensitive applications, only a minority of the team are working on the hot spots. And speaking about costs, knowing what to optimise is the key to software performance. Improving the performance of an operation by 10000x will improve the performance of your program by less than 1% if the operation is only 1% of the profile to begin with. So I'm only saying that most developers don't work on code where the layout is very significant, but some certainly do.
- coolThingsFirst 4mo agoWhy doesn’t the machine fill up the other cache lines as well why is 64 bytes only and then a miss?
- masklinn 4mo agoThey will absolutely do that (prefetching, they can even eagerly load what’s on the other side of a pointer). However it requires additional hardware to recognize patterns which benefit from prefetching, and every time the CPU prefetches data which ends up not being used it has both burned energy and memory bandwidth, and evicted data from the cache which might be needed (cache pollution).
- Liquid_Fire 4mo agoIt might sometimes prefetch the surrounding lines as well, but ultimately cache space is limited, so there is a trade-off. Every time you fill a line, you are throwing away something else that was cached there previously, which you may need again in the near future.
- spiffyk 4mo agoA cache line is simply the unit of data a CPU cache works with (generally 64 bytes, because someone somewhere has probably determined that that is the best line size for general use), much like there are units of data like bytes (8 bits nowadays, but there have been weird ones historically), pages (varies between hardware as well, and may be OS-configurable), etc. As TFA mentions, a CPU does some predictions about what cache lines to prefetch, e.g. when you do sequential reads. Moreover, the x86_64 instruction set provides a prefetch instruction through which you are able to give the CPU a hint "hey, I'm gonna be using this soon, prepare accordingly, pretty please". Still, the utility of prefetching is diminished if you only use a single byte from each cache line, because the mechanism generally depends on you doing other work while the next cache line is being fetched. So really the best case scenario is to take as much time as possible to work with what is already fetched, so that there is time for the next unit of data to be fetched in the meantime.
- RickJWagner 4mo agoThat’s a great read. I wish more people wrote like that.
- fdegmecic 4mo agoCppCon 2014: Mike Acton "Data-Oriented Design and C++" Andrew Kelley: A Practical Guide to Applying Data Oriented Design (DoD) you should check these two talks out then.
- lionkor 4mo agoThe first is quite famous in data oriented design/programming circles, the second one is up there, too. Both very much worth watching.
- setheron 4mo ago(author) thank you for the kind words.
- deleted 4mo ago[deleted]
- AxelWickman 4mo agoCool read. The AoS vs SoA speaks for itself.
- Luff 4mo agoYes we should end the hateful rhetoric of most and least significant bytes. Every Byte Matters.
- zabzonk 4mo agoWe need an ending to byte-sizeism as well.
- diabllicseagull 4mo agoWe'll get there, bit by bit.
- moi2388 4mo agoIn combination with “What colour are your bits” I do not see this ending well..
- deleted 4mo ago[deleted]
- moring 4mo agoThe article shows nicely how "every byte matters" is false. First, it starts off by talking about the cost of a new field, when the actual topic is array-of-structs vs. struct-of-arrays. Then, this: > How much of an impact can this have? > Reading is:alive (1 byte) Across 1M Monsters You aren't reading one byte here, you are reading 1M bytes! Of course, optimizing the access to 1M bytes is something to consider. Optimizing the access to one byte isn't. The article is definitely worth reading IMHO, but it really needs a better headline!
- jayd16 4mo agoEven more so, it shows that SoA data structure means you can add fields to your 1M monsters with little impact.
- celrod 4mo agoYes. I think one of the big advantages of SoA is that you only pay for the fields you're currently using. If you need a field somewhere, you can add it and only pay the cost of iterating it where you need it.
- notatyrannosaur 4mo ago> you can add fields to your 1M monsters with little impact. Great for this access pattern, but I wouldn't make a general statement like that. This is the same thing as row-oriented vs column-oriented databases, OLTP vs OLAP. SoA is weak if you are adding/removing monsters more often than accessing a single "hot" field.
- Altern4tiveAcc 4mo ago> SoA is weak if you are adding/removing monsters more often than accessing a single "hot" field. Why is that? Genuinely curious. Does "weak" mean that it performs worse than AoS, or that the gains aren't as significant versus AoS?
- jayd16 4mo ago
- burnt-resistor 4mo agoI'm curious if anyone has had to write a JNI extension for a hot (CPU, GPU, RAM) section the JVM was unable to effectively JIT and/or optimize enough.
- chadgpt3 4mo agoI think that's generally a pessimization because JNI has fairly high overhead.
- burnt-resistor 4mo agoSo then, preferably the set of problems that take a long time to compute and does so independently.
- nasretdinov 4mo agoIdeally you'd want to go further and actually store the is_alive as a bit mask and use SIMD instructions to filter out zeroes for example.
- chadgpt3 4mo agoEven without SIMD (not counting SWIR as SIMD) if you knew most entities were alive, you could zoom through the array checking 64 at a time until you found one that wasn't all 1s and then inspect more closely. Though maybe an index list is better if they're almost all alive.
- recursivedoubts 4mo agoWhen you are developing games, sometimes. When you are developing most other applications every byte does not matter. What matters much more is overall system architecture, collapsing unnecessary abstraction layers that some developers (especially java developers) seem to love and optimizing your datastore access. As always, profile profile profile. A company I worked for spent a violent couple of man-decades flipping our proprietary scripting language from interpeted to bytecode generation, obviously with tons of bugs and subtle semantic changes, and it ended up boosting overall system performance by about 30%. We could have done nothing over that period of time and hardware advances would have made a bigger impact.
- SuperV1234 4mo agoData Oriented Design rocks. It was the subject for my CppCon 2025 keynote: https://youtube.com/watch?v=SzjJfKHygaQ https://youtube.com/watch?v=SzjJfKHygaQ
- rao-v 4mo agoAnyways find it odd that major languages don’t have a built in way of asking for an array of objects to be optimized as SoA or AoS
- jayd16 4mo agoIt doesn't quite make sense to keep object identity at the language level. Inherently the data in the arrays cannot be the same memory of the data in the objects fields. To get the speed up, you can't just abstract it as an access pattern because it's tied to the specific way the memory is laid out. If you were trying to make some kind of collection type that could be queried by both row and column, you would need to store it both ways at all times and also keep both representations in sync, which also defeats the purpose, somewhat. I feel like if you're trying to do this pattern then it doesn't make sense to also keep the objects.
- chadgpt3 4mo agoDatabase structure in a programming language! I think I've heard this idea before but never seen it implemented. Define a table with rows and columns, give it some implementation hints and query it like "SELECT id FROM monsters WHERE alive=false" and the compiler could translate it several completely different ways depending on your layout.
- rao-v 4mo agoYes! Exactly
- rao-v 4mo agoI was thinking about just asking the compiler to do the expensive reshuffle when I need it to, but you could go further and expect the compiler to figure out the likely access pattern and spend the transformation budget. Heck memory is cheap (fine was cheap) give me a data structure that amortizes writes cleverly by maintaining both SoA and AoS at the same time
- 4mo ago
- ChrisMarshallNY 4mo agoI started off with Machine Code, on a device with 256 bytes (not KB) of RAM. That was 256 bytes, to install the executable, reserve the stack, and set up the heap. We often used bit (not byte) fields, to convey information. Made life challenging. However, being able to be sloppy has its definite advantages. It takes a long time to design highly-optimized stuff. If just declaring a couple of new properties takes thirty seconds, and designing a bitfield takes an hour, then we have some real cost-savings, there. That said, it's easy to get crazy, these days. I just spent a couple of days, chasing down greedy memory hogs. These were operations that ate gigabytes of memory. I determined that the real culprit was actually Apple MapKit, and figured out a simple workaround, but it took a long time to get there. If I suspect the OS, then it's usually my fault, and trying everything before going back to the OS takes time.
- Obscurity4340 4mo agoHow do you deal with all the daemons and automatic crap that does this on Mac? Isnt it all reinforced by SIP?
- ChrisMarshallNY 4mo agoI think all operating systems have these. In this one case, allocating a MapView via storyboard, caused some kind of cascading strong reference stuff. Simply allocating it programmatically, fixed it. Took awhile to get there, though.
- Obscurity4340 4mo agoCan you elaborate? Did you turn SIP off or what did you do?
- ChrisMarshallNY 4mo agoNo. No need. Just spent a lot of time, logging my code, and eliminating every possible leak. Tedious, but there really wasn’t anything else I could do. Finding out about the programmatic solution was really just a wild guess.
- readthenotes1 4mo ago"In that time, you get used to huge classes. New functionality? Just add a new method and field to the class" I guess this is one reason why object-orientation has such a bad reputation. I once worked at a bank where the OO mentor had taught people that the only object they needed was "Tape" and have them replicate the structure of data on the old spooled tape reels. The struct of arrays reminds me of this optimization.
- compiler-guy 4mo agoSoA can be a big win. But so can plain AoS, just depends on the access pattern. Profiling important workloads matters. Without that everything else is guesswork.
- manoDev 4mo agoTip: to get LN cache sizes on mac, the commmand is $ sysctl -a | grep "l.*cachesize" | gnumfmt --field=2 --to=si hw.perflevel1.l1icachesize: 132k hw.perflevel1.l1dcachesize: 66k hw.perflevel1.l2cachesize: 4,2M hw.perflevel0.l1icachesize: 197k hw.perflevel0.l1dcachesize: 132k hw.perflevel0.l2cachesize: 13M hw.l1icachesize: 132k hw.l1dcachesize: 66k hw.l2cachesize: 4,2M And the equivalent to LEVEL1_DCACHE_LINESIZE is $ sysctl -a | grep hw.cachelinesize hw.cachelinesize: 128
- agalunar 4mo agoPerhaps worth noting that the number of lines in a cache is often different than the number of rows, which can be relevant for some workloads. The size of an ordinary cache is rows × ways × size(line), where rows = 2 ↑ num-idx-bits. For example, most Intel 64 and AMD 64 processors use log₂(size(page)) − log₂(size(line)) = 12 − 6 = 6 index bits for the L1 cache*, so an L1 cache with 8-way associativity is 64 sets × 8 lines/set × 64 bytes/line = 32 KB large, and an L1 cache with 12-way associativity is 64 × 12 × 64 = 48 KB large. I remember being surprised to learn that most processors have only 64 rows in the L1 cache! *So that virtual indexes and physical indexes are identical (so that retrieval of the row can happen in parallel with TLB lookup).
- jadbox 4mo agoZig's MultiArrayList is a cool language feature to support objects of collections, and I wish more languages had first class support for it (without overhead of copy's).
- PrathikArun 4mo agoWow looks great!!!
- onesingleblast 4mo ago[dead]
- johnthescott 4mo agoif truly "every byte matters" then why java?