[HN Gopher] The compiler will optimize that away
___________________________________________________________________
The compiler will optimize that away
Author : 0-_-0
Score : 321 points
Date : 2021-05-01 23:38 UTC (1 days ago)
(HTM) web link (blog.royalsloth.eu)
(TXT) w3m dump (blog.royalsloth.eu)
| polishTar wrote:
| Why can't a compiler optimize that? Perhaps I'm naive, but it
| seems totally possible for a compiler to have some sort of
| heuristic that allows it to do this transformation behind-the-
| scenes when it makes sense
| Blikkentrekker wrote:
| The ironic part is that the compiler can't because the
| languages are fairly low level for performance's sake.
|
| _C_ and _Rust_ make various guarantees about data layout which
| programmers rely upon. _C_ compilers cannot turn an array of
| structs into a struct of arrays because the layout of arrays is
| guaranteed, and needed to make common and trivial _C_ accesses
| work which work by pointer arithmetic.
|
| Turning an array of structs into a struct of arrays is indeed
| an optimization that would be possible in a language that did
| not make such guarantees about data layout. Perhaps it would be
| interesting to have a data structure that provides less
| guarantees on lower level access that in turn could be more
| aggressively optimized.
| gpderetta wrote:
| It is technically also possible in C as long as the program
| can't tell the difference (thanks to the as-if rule). This is
| hard to prove though and in practice requires whole program
| compilation.
| Blikkentrekker wrote:
| The _C_ standard provides guarantees about data layout even
| if the compiler can prove it will never be used internally,
| for the sake of, say, external debugging.
| lostmsu wrote:
| Which sections of the standard are you referring to?
| gpderetta wrote:
| And C compilers routinely optimize structures in such a
| way that they are impossible to inspect in a debugger.
| Because of SRA, non escaping aggregates might even not be
| allocated on a memory location at all.
| Blikkentrekker wrote:
| What is an "escaping aggregate"?; this term has 80 hits
| on _Google_ , none of which seem to be related to
| programming.
|
| Looking it up "aggregate" is a _C++_ term for which the
| standard indeed does explicitly allow various
| optimizations and guarantees about data layout are
| weakened; this is not the case with _C_ arrays.
|
| Do you have any practical evidence to _C_ compilers
| altering the data layout of an array in any way?
| throwaway17_17 wrote:
| GP's comment is referring to Scalar Replacement of
| Aggregates on the one hand. This is an optimization where
| by the compiler will, for a function with a Struct
| argument, only pass in a pointer to / copy of the actual
| element of the struct used inside a function. Hence the
| name, the compiler is replacing the aggregate struct by
| one or more of the individual values it contains.
|
| The reference to non-escaping aggregates, describes the
| compiler optimizing a local only struct by keeping the
| individual elements in registers during usage, and
| because the struct as a whole does not leave the local
| scope, these elements are just discarded when that scope
| closes.
|
| Both of these transformations/optimizations are performed
| by all three industrial C and C++ compilers
| (gcc/llvm/msvc).
| gpderetta wrote:
| Exactly. Thanks.
| astrange wrote:
| It doesn't require this as long as the program has the
| same side effects - the contents of memory are not a side
| effect necessarily.
|
| The problem is that there are lots of operations which
| imply the existence of part of an object, like passing
| around pointers or allocating single objects at a time,
| and that would disable these layout optimizations. An
| optimization that's easy to break isn't useful even if it
| would help a lot; you want them to be predictable more
| than anything.
| xiaodai wrote:
| Something I genuinely don't understand "buttery smooth 3D game"s
| are created with C++ too but why isn't C++ an issue for games?
|
| Seems to contradict the premise which is that programming
| languages were designed in the past and hence not efficient for
| modern computers.
| mort96 wrote:
| If you use C++ in the "default" object-oriented way, your
| program won't be as efficient as it could've been. Computers
| are fast, so you can still make games which run well, but not
| as well as they could've been.
|
| C++ can also be used in ways which are really fast on modern
| machines. You can, for example, write code in a data-oriented
| "struct of arrays" approach with C++. The argument is just that
| C++ isn't designed with that in mind, and that a high
| performance language designed today could be designed with
| modern machines in mind.
| kaba0 wrote:
| > If you use C++ in the "default" object-oriented way, your
| program won't be as efficient as it could've been
|
| That's way too general to hold any truth. C++'s classes are
| zero cost, SoA vs AoS is not always relevant. If I have three
| instances of my class, each doing something compute-intensive
| in a simple method, how is it different to dod?
|
| Also, even virtual functions are not necessarily any more
| expensive than the alternative if one knows what he/she does.
| mort96 wrote:
| The context of the discussion is one where the difference
| between SoA and AoS is significant. It's not always true in
| general that your program won't be as efficient as
| possible, but it's true in the context we're discussing,
| where you have a lot of things (entities or whatever) and
| you're iterating through them, which is what is being
| discussed.
|
| I was writing a direct answer to the question of "how can
| it be true that C++ isn't built for modern machines even
| though modern games with good performance on modern
| machines use C++". I think the answer is entirely correct
| in context.
| kaba0 wrote:
| Fair enough, sorry I misunderstood your point.
| chromanoid wrote:
| C++ is an issue for games. Most game engines implement a
| garbage collected script language (e.g. Lua, CLR,
| JavaScript...) to implement huge parts of game logic.
| mort96 wrote:
| They're not implementing game logic in those languages for
| performance reasons though. The question, as I interpret it,
| is about why C++'s performance isn't an issue for games.
| Using those languages for scripting/game logic is a trade-off
| where performance is sacrificed for developer productivity.
| chromanoid wrote:
| You are right. I misunderstood the question.
|
| I thought it was about the language as efficient tool not
| about performance. The Unreal engine is not even using data
| oriented design (at least for most parts), so I was
| assuming C++ sufficient efficiency was out of question. I
| agree with your argument, that the games would probably be
| faster. Game object processing is probably not the most
| pressing issue in games regarding performance, so I guess
| the impact would be not that visible (when talking about
| the stuff that is implemented in C++ in game engines).
| einpoklum wrote:
| This article makes a bunch of invalid (sometimes implicit)
| assumptions or claims:
|
| *A 20 yro language's design decisions don't make sense today
| because of the hardware perf situation change.*
|
| 1. Most languages were not designed for optimal utilization of
| the specific hardware situation during their conception.
|
| 2. Even languages designed with performance in mind somehow are
| still incredibly general and flexible things.
|
| 3. The article itself shows that CPU hardware has been changing
| along a mostly predictable path for decades. Everyone knew
| Dennard scaling would end, even if you couldn't predict it to
| within the single year. Everyone saw CPU latency was decreasing
| much much faster than memory latency, already in the 1980s.
|
| --
|
| * The main problem that we have today lies in utilizing the CPUs
| to their full potential.*
|
| This is _a_ main problem, not _the_ main problem:
|
| * There are other, specialized processing hardware (like GPUs).
|
| * There's computation happening elsewhere on the system, e.g. on
| disk controllers and NICs.
|
| * And actually, if you look at desktop environments and apps:
| They really don't need to utilize the CPU to its full potential;
| they're a mess of entanglement of things which get in each
| other's way and it's difficult to figure out why your system ends
| up being sluggish despite your brand-new shiny hardware.
|
| --
|
| *Another problem with the current programming languages is that
| they didn't evolve beyond the C-style type of programming. *
|
| * C-style type of programming is just fine for C-style kinds of
| programming tasks. Every language has strengths and weaknesses.
| Some pairs of languages have a more obvious "X is an evolved Y"
| or "X is a better Y" relationship, but not that many.
|
| * Most languages do not evolve or change to exploit specific
| hardware capabilities. Or at least - few language changes are
| intended to achieve that.
|
| --
|
| etc.
|
| It's not a junk article though. The data orientation of program
| design is a useful notion, in many programming languages. It's
| just that, instead of reading this blog post, which quotes Mike
| Acton, you can just watch Acton's talk on this principle:
|
| https://www.youtube.com/watch?v=rX0ItVEVjHc
| tialaramex wrote:
| Another example:
|
| > "Use const and the compiler will optimize that away!" Nobody
| knows what the compiler will do, if nobody checked what the
| compiler did.
|
| Rust (in the Rustonomicon if nowhere else) specifically
| promises that const gives you the same results you'd get by
| calculating and manually inlining the constant itself
| everywhere. It even spells out all the things you thus can, and
| can't do in a constant so that your compiler can definitely
| correctly evaluate the constant from the point of view of the
| target architecture despite potentially different hardware.
|
| e.g. if you tell Rust const BAR :isize =
| isize::from_be(0x1234);
|
| You've conjured a constant BAR whose size and data layout
| depends on the target architecture, but the language promises
| that figuring this out incurs no runtime cost, the work will be
| done at compile time.
|
| Indeed Rust will refuse to compile your program if it can't
| figure out how to do this, explaining what went wrong, e.g.
| that same above snippet would not have worked years ago,
| because isize::from_be() was not yet marked as suitable for
| such endeavours and so the compiler wouldn't attempt it.
|
| If you mark a function the compiler can't evaluate as being
| suitable, the compiler will reject that too, even if you don't
| use the function, somebody else might and it won't disappoint
| them.
| commandlinefan wrote:
| I've never been able to get my head around the "anti-
| optimization" mindset. Like... what else should we be doing? If
| you're going to be writing code anyway, you might as well take
| optimal advantage of the hardware while you're at it. Now, some
| people point out that attempts to optimize sometimes backfire,
| but that's a different problem - that's something you should work
| on improving though practice (and measurement).
| fwip wrote:
| "Anti-optimization" advocates doesn't say "optimized code is
| bad." It means "other things are more important than
| optimization."
|
| One of those things is the amount of work required. For
| example, if it takes 30 minutes to get a "good-enough"
| solution, and 2 days to get a "very optimized" solution, most
| of the time you should go with the good-enough solution,
| because most of the code you're writing isn't performance-
| critical.
| truth_seeker wrote:
| Extremely well written and communicated.
| albertzeyer wrote:
| Another orthogonal aspect with modern hardware is the increased
| amount of parallel execution. Most of the standard programming
| languages are not really designed to support this well. So we use
| extensions like CUDA. But this is not really general purpose but
| only for GPU.
|
| Once we reach maybe 100 cores, or 1000 cores, or some orders of
| magnitude more cores in the CPU, we have to have better general
| purpose language support for this. Most memory is local for some
| subset of cores. There are warps which execute the same code
| (SIMT).
| astrange wrote:
| What's this "once"? This was a theory of future CPUs from maybe
| a decade ago that totally hasn't happened. We're instead
| getting increasingly smaller few-core CPUs in things like
| smartwatches. In laptops, M1 replaces a hyperthreading CPU with
| one that runs a single thread much faster.
| CameraSupra wrote:
| Why aren't hardware designers designing hardware for the
| languages and methods we have?
| astrange wrote:
| They are (through prediction, reordering, etc in the CPU), but
| manual approaches like hinting or VLIW have always turned out
| to fail, and the automatic approaches we're using now turn out
| to have security issues like Spectre.
| sesuximo wrote:
| There are physical limits. E.g. clock speed can't go so high
| that information must travel across the cpu faster than the
| speed of light. E.g.2. As caches get bigger, it takes more time
| to index them.
| jakear wrote:
| Article seems to suppose the entities will only ever exist in one
| collection. If that's the case then sure remap the field layouts
| to behave better in cache. But the second you want to reference
| only a single element of that collection, or reference many
| elements of many different collections, you're now dealing with
| managing N pointers for N fields, which of course all get
| invalidated when the collection is modified, and ha w lifetimes
| tied to the lifetime of the collection. As opposed to the single
| object with its own lifetime. And of course all the cache
| locality arguments go out the window.
|
| The takeaway seems to be: if the entity in an atomic collection
| of items, represent that collection as _the entity_ in as
| efficient manner as you see fit, rather than crating entities for
| each one. This makes sense, and is not out-of-line with OOP
| reasoning: each object corresponds to an atomic logical unit.
| Bancakes wrote:
| Wish normal people witnessed how few innovations are being
| introduced on the CPU/GPU side in the 21st century. The high-
| performance software industry is low-key carrying the hardware
| architecture industry on its back. Shaders, pipelines,
| coroutines, etc are there to make the programmer's job easier in
| the end.
|
| On my old nokia 3110c, I could access my calendar and
| appointments instantly by clicking right on the d-pad. On my
| Nokia Lumia 710, I had to wait for a splash screen. On my Nokia
| 3.1, I had to wait for internet synching. The difference between
| slow and fast software far outstretches that of slow and fast
| hardware.
| eska wrote:
| > The high-performance software industry is low-key carrying
| the hardware architecture industry on its back
|
| I don't understand this statement. To me it evidently looks
| like the opposite: the software gets worse (because most
| programmers don't even learn how CPUs/GPUs really work), and
| the hardware has to make up for it.
|
| The reason the calendar loads up slower is not because the
| hardware got worse, it's the software.
| CyberDildonics wrote:
| I can't believe anyone would say this, it is basically the
| opposite of reality.
|
| CPUs and GPUs have come a long way in two decades. Even CPUs
| are incredibly fast, but between memory allocations, memory
| layout, multi-threading and just weeding out terrible
| algorithms, most software can probably be sped up by 100x.
|
| > The high-performance software industry is low-key carrying
| the hardware architecture industry on its back.
|
| That doesn't even make sense. Do you think GPUs aren't more
| powerful than 20 years ago and that the difference between
| quake 3 and Order 1888 is software?
|
| > On my Nokia 3.1, I had to wait for internet synching. The
| difference between slow and fast software far outstretches that
| of slow and fast hardware.
|
| Right - doesn't this contradict everything you just said?
| Bancakes wrote:
| These are steady, predictable, iterative improvements. We
| have nothing groundbreaking like 1970s-1990s. Of course I'm
| not saying modern hardware is slow, but it's not progressing
| like it used to.
| CyberDildonics wrote:
| That is both not true and not what you said at first.
| Transistor density has continued to rise but there is only
| so much you can do when people mostly run javascript on a
| single thread.
|
| There were a lot more break throughs in the early days of
| electronic components too because everything was new.
|
| CPUs are a world away from where they were two decades ago,
| but people don't notice because typing into a facebook
| window still lags. It is an incredibly superficial nonsense
| way to look at what has really happened at the hardware
| level.
| astrange wrote:
| If you only have a single core (common on battery powered
| devices) then you should only run a single thread. Excess
| parallelism doesn't make things go faster and uses more
| memory. The important thing is avoiding unnecessary waits
| on non-CPU resources like I/O.
| CyberDildonics wrote:
| This reads like a reply to a different comment. The
| person above was saying there haven't been any
| advancements in CPUs or GPUs which is obviously not true.
| No one said anything about battery powered devices and
| even those have had multiple cores for many years now -
| as a result of transistors still shrinking.
| astrange wrote:
| They're multicore devices but often only one core is
| free. Depending on how important you are, the rest are
| busy doing other things and you're not going to get
| scheduled.
| CyberDildonics wrote:
| This thread was someone saying there haven't been any
| advances in CPUs and GPUs in general.
|
| You seem to be in a completely different goal post
| shifting context of some mythical mobile device that has
| only one core, but actually has multiple cores, but all
| them are pegged at 100% all the time. This doesn't make
| any sense in any context since it isn't even true, but it
| has nothing to do with what you are replying to.
| andi999 wrote:
| Why wouldn't a new programming language like python nowadays
| decide to use a Gil? I thought the point was to make the
| interpreter easier to write.
| toast0 wrote:
| A global interpreter lock made sense in the 1990s when multiple
| cpus was rare, and when it happened, it was usually two.
|
| Now, it's hard to find a system with just one cpu. Maybe
| something really embedded; but IoT stuff sometimes is
| multiprocessor these days. Anything intended for regular
| computers needs to be prepared for 1-16 cpus, and more isn't
| hard to find if you need it. Building a new programming
| language with a big constraint preventing it from using modern
| computing seems like a mistake. That doesn't mean an early
| version wouldn't start with a big lock, but fine grained
| locking is probably needed before release.
| conradludgate wrote:
| If you want to utilise this data oriented approach in rust, check
| out soa-derive[0]. SoA stands for struct of array, rather than
| the conventional array of struct.
|
| [0]: https://crates.io/crates/soa_derive
| d0tnet wrote:
| This is amazing!
| teh_matt wrote:
| I hacked up something similar (https://gitlab.com/mbryant/funct
| iontrace/-/blob/master/src/p...) a while ago. Your variant
| looks much more useful!
| conradludgate wrote:
| Yeah I'm a big fan of proc-macros. If utilised well, it can
| do some really great things
| pif wrote:
| Bullshit!
|
| The most important feature for the utter most of enterprise
| software is to be maintainable over requirement, managing and
| employee changes.
|
| Such optimizations as those described in the article make sense
| only in a handful of situations, and using them must not be a
| light-heart decision, because their cost is big.
|
| I fully agree with going all in when it makes sense, but we all
| know what is the root of all evil.
| CyberDildonics wrote:
| > Such optimizations as those described in the article make
| sense only in a handful of situations, and using them must not
| be a light-heart decision, because their cost is big.
|
| This is really not true. Inheritance is not only slow it is
| needlessly complicated. It introduces dependencies and
| indirection while gaining nothing.
|
| People who have never programmed before think having a car
| class inherit from a vehicle class makes sense. Eventually they
| realize that what they have are arrays of positions and
| velocities that they need to combine. That could be multiple
| files with lots of boiler plate nonsense or it could be a
| single loop.
|
| > I fully agree with going all in when it makes sense, but we
| all know what is the root of all evil.
|
| If you are implying optimization, you should realize that that
| quote comes from someone telling his students not to noodle the
| tiniest details like post or pre increment in their for loops
| to get the compiler to output one less instruction when their
| program isn't even done yet.
|
| If you want modern software to run fast, you need to architect
| for it first. Then you can make things work, then you can
| profile and optimize. This idea that there are no consequences
| to the early design decisions you make is a gross distortion of
| the context of this quote.
| jayd16 wrote:
| The real answer is you almost never need to solve this silly
| problem. If your data is static, why are you counting through
| arrays? If its dynamic, your n is probably low enough it doesn't
| matter or you're using some better data structure than an array.
|
| If you do hit this kind of problem its usually in games (lots of
| dynamic user generated content) and even then the n is usually
| negligible. The parts where this does matter are already very
| data oriented. (Although not just in the way the article implies.
| Think vertex buffers and bitmaps.)
|
| Point is, languages could probably add some kind of column
| oriented type of array and compilers could efficiently loop
| through them but it probably wouldn't really help all that much.
| bigbizisverywyz wrote:
| Would have to agree, common data structures like linked lists
| and maps can't really be easily composed into this sort of
| sequential layout as required, and the main advantages only
| come about when you're searching through the data for
| something.
|
| The other data we process a lot of is strings which are
| commonly variable in length and scattered around in separate
| mem allocations.
|
| Still, the performance difference between the 2 examples was
| eye-opening so I'll definitely spend a bit of time looking at
| Data-Oriented Design book for some ideas.
| bvrmn wrote:
| > 4. Data oriented way of programming has its own set of
| problems.
|
| This should be a top reason. Author is too shy to reveal the true
| cause.
| typon wrote:
| Data oriented programming isn't a framework or paradigm. It's
| simply a principle that says that programs should be written to
| solve particular problems on particular hardware. If objects
| serve a purpose to this end, then they are also data oriented
| programming.
| bsdubernerd wrote:
| Data-oriented programming is not hard by itself, rather as a
| consequence of how programming is done and what sort of
| flexibility you get in return.
|
| The traditional (or should I call "inverse") approach is to
| define what the program should do and make it modular enough so
| that parts can be changed easily. For this to work well,
| functions are given a state snapshot to read/operator or
| change. Encapsulating this state into a struct/object allows
| functions operating on the same data to be extended easily,
| without having to think which functions do what, when and where
| they're called.
|
| Thinking about locality though does force you to think not only
| about program structure, but overall program flow, which can be
| much, much harder to implement in a flexible fashion.
|
| Struct-of-arrays or arrays-or-structs has little to do with it,
| although it's a common pattern seen when working with some kind
| of highly structured datasets.
|
| Look at high-performance graphics pipelines to see how this
| affects the structure of the code. There's a lot of duplication
| which is required to handle data which is structured in a
| different way for efficiency, something which is not solved by
| just SOA/AOS swapping, but requires thinking of what data is
| used, what are the access patterns of the algorithm and how to
| pack the data so that locality is optimized. It's also harder
| to change as a consequence.
| bvrmn wrote:
| I have experience with performant cache-friendly code. Yes,
| data oriented approach can be useful in some fields. But
| author blames the whole industry having low demand of
| performant code and wants python to accommodate cache-
| friendliness. This is a high level BS.
| theamk wrote:
| Summary: the computer has changed -- memory latency measured in
| CPU cycles has grown a lot. So we should not be using traditional
| struct/class-like programming model, where we put all properties
| of an object next to each other. Instead, we should be using
| game-style "data oriented programming" a.k.a. "column databases"
| for a much higher performance.
|
| However, most modern languages (C, C++, Python, Java, etc..) are
| not making it easy, and this is bad! Meanwhile you can data into
| columns and enjoy faster programs.
| antpls wrote:
| columnar layout is only faster if reads are sequential
| cma wrote:
| If things work one or a few columns at a time this isn't
| necessarily true. Prosumer CPU L3 caches are already up to
| 128MB and will be up to 1GB before long. If one or a few full
| columns fits fully in cache but all the data doesn't,
| columnar layout may still be faster in the face of random
| access within the column.
| mjevans wrote:
| Structs are easy to envision as a repeating pattern
| tessellating across memory space; it makes slicing regions of
| data and bulk memory copy (e.g. DMA) easy.
|
| Yet if the algorithms mostly work with subsets of those fields
| then a 'column database' type of data layout clearly has
| benefits.
|
| Though for the complex case I would prefer more syntax sugar
| from the language.
|
| A "column struct" datatype that might include tuning
| suggestions for stride, size, etc; but would present a simple
| interface for humans. Given multiple underlying blocks of
| records a parallalizable foreach style loop might even shard
| across NUMA distributed threads if supported at runtime.
| tom_mellior wrote:
| > However, most modern languages (C, C++, Python, Java, etc..)
| are not making it easy,
|
| The article shows how easy it is to declare a struct of arrays
| in Java: You declare a struct (class). Inside it you declare
| arrays. Done.
| beagle3 wrote:
| It is easy to declare, but it is not easy to use:
|
| You can pass the whole thing, but if you want to pass a
| subset of it? just one element?
|
| When you want to extend it, you have to extend many
| individual arrays, etc. When you want to copy entry 17 to
| entry 50, you can't loop over the fields - you have to spell
| out every field.
| tom_mellior wrote:
| A subset of an AntColony is another AntColony containing
| sub-arrays. One element of an AntColony is an Ant which you
| can instantiate with the data at the appropriate index from
| each array. I'm not trying to be difficult, I really don't
| see the problem with implementing these basic programming
| tasks.
|
| I agree that spelling out the fields can become tedious if
| you have a lot of structures of arrays or lots of fields
| per structure. A 100-line Python script can generate all
| this code for you if you prefer. Sure, it's a "limitation"
| of most programming languages that they don't provide this
| out of the box. But not a big limitation, I'd argue. Many
| languages even have features that allow you to write this
| functionality as a library, 100% inside the language
| (Python and Java annotations, for example).
| beagle3 wrote:
| > I really don't see the problem with implementing these
| basic programming tasks.
|
| That you have to write them over and over again for every
| single class. You offer to do that with Python.
|
| > But not a big limitation, I'd argue. Many languages
| even have features that allow you to write this
| functionality as a library, 100% inside the language
| (Python and Java annotations, for example).
|
| This is an argument of the "all languages that are Turing
| complete are essentially equivalent" kind. True, but not
| interesting in this context.
|
| Ecosystem matters. If your Java program is the only one
| that does these things, it may just as well not be Java.
| You will be unable to use any other library without back-
| and-force adapters, for example.
| tom_mellior wrote:
| > That you have to write them over and over again for
| every single class. You offer to do that with Python.
|
| Or with some other code generator. A mythical language
| with SoA/AoS transformation "built in" will do exactly
| the same under the covers. But it might not offer other
| features you are used to, or (your argument) a
| sufficiently rich library ecosystem. You might as well do
| it yourself and use a language you are otherwise familiar
| and happy with.
|
| > This is an argument of the "all languages that are
| Turing complete are essentially equivalent" kind.
|
| Is it? You can't do what I described in C, for instance.
| To be clear, what I meant when I wrote "as a library,
| 100% inside the language" was that, given the appropriate
| definitions _in a library, not in an external tool you
| run before your build_ , you just write something like
| the following Python: class Ant:
| someField = ... someOtherField = ...
| @StructureOfArrays class AntColony:
| pass
|
| or something the following Java: class
| Ant { int someField; boolean
| someOtherField; } @StructureOfArrays
| class AntColony { }
|
| and get all the features you can dream of generated for
| you inside the AntColony class, which you then use
| exactly as if you had typed things out yourself. You
| can't do this in C. This isn't an "all Turing complete
| languages" argument as far as I can see.
|
| > You will be unable to use any other library without
| back-and-force adapters, for example.
|
| I don't understand what you mean. In the above example,
| the Ant class exists, so you don't need adapters. The
| arrays inside the AntColony class exist (once the
| annotation processor is done with them), so you don't
| need adapters.
| beagle3 wrote:
| > You might as well do it yourself and use a language you
| are otherwise familiar and happy with.
|
| Nim, Rust, D, Lisp support this already as compiled
| languages, without sacrificing anything - perhaps also
| Zig - at the macro level. Jai offers it as a language
| feature, but I don't think that's a particularly good
| idea - it's just that Jai tries to stay simple in such a
| way that doesn't let it be a user-level thing.
|
| > Is it? You can't do what I described in C, for
| instance.
|
| Not exactly, no, but you can with "X-macros", and I have
| - in fact - been doing that since 2005 or so, using only
| features from C89 ; I admit I have not used Java in the
| last 10 years, and then it was impossible - maybe some of
| the revisions in the last decade made it possible "inside
| the language" - what's the name of the feature I need to
| look up?
|
| > I don't understand what you mean. In the above example,
| the Ant class exists, so you don't need adapters. The
| arrays inside the AntColony class exist (once the
| annotation processor is done with them), so you don't
| need adapters.
|
| And now, you try to use the standard library "Sort" or
| "Find", it doesn't work without adapters, even if you
| have comparators defined for Ant -- and many other things
| that assume that objects are not part of a colony.
|
| It's hard to explain in a short post. I occasionally use
| a language called "K" of the APL family, which makes all
| of this incredibly trivial from the get go. It's not a
| new language - K itself IIRC predates Java by a couple of
| years, APL (of which K is often considered a dialect) was
| first specified in 1958 and implemented in 1962. It
| basically considers everything an array - one or multiple
| dimensional - and field access is nothing more than
| having one of the axes specified by symbols rather than
| numbers. This makes everything incredibly simple and
| uniform to an extent that is hard to describe to someone
| who has not experienced it. (And indeed, in the turing
| sense it makes no difference .... but if you implement
| your Java objects as "mapping from field to value" you
| may get some of the benefits but will not really be using
| Java or be able to use the ecosystem)
| tom_mellior wrote:
| > Not exactly, no, but you can with "X-macros",
|
| I only gave this a passing thought before and was too
| quick to dismiss it. Yes, I see how it can be done. Which
| is just another argument that the whole thing is a non-
| issue, no? Languages with macro systems can do it.
| Languages with other build time code generation systems
| can do it as well.
|
| > what's the name of the feature I need to look up?
|
| Annotation processors. Here's the first thing I found
| with an example of code generation:
| https://cloudogu.com/en/blog/Java-Annotation-
| Processors_3-Ge.... Yes, it's considerably more verbose
| than a macro system. But it's still a tiny, write-once
| (or completely off-the-shelf) part of your overall
| project.
|
| > And now, you try to use the standard library "Sort" or
| "Find", it doesn't work without adapters
|
| OK, but the same is true for all the macro based systems
| you mentioned above, isn't it? And if it isn't: whatever
| you can generate with macros you can also generate some
| other way.
|
| Edit: You won't like this, but looking at the docs for
| Java's Collections.sort method (https://docs.oracle.com/j
| avase/7/docs/api/java/util/Collecti...: "This
| implementation dumps the specified list into an array,
| sorts the array, and iterates over the list resetting
| each element from the corresponding position in the
| array." So you can use it just fine if the SoA implements
| the List interface. But yes, at the cost of temporarily
| materializing an array of structs.
| beagle3 wrote:
| That what I refer to as the "Turing complete" argument.
| Yes, you can definitely do all of those. But almost no
| one does - and those that do, usually silo themselves
| from the ecosystem.
|
| Yes, if you like Java, you can definitely implement a
| Java compiler/interpreter in C and get everything you
| like about Java using C. But no one does.
|
| Thanks for the reference, I'll read about annotation
| processors later tonight.
| tom_mellior wrote:
| I appreciate the discussion, but I believe you're arguing
| in bad faith.
|
| > Yes, you can definitely do all of those. But almost no
| one does
|
| <shrug> That's what you say. What is this based on,
| considering that until a few hours ago you didn't even
| know that Java has this built-in code generation
| facility?
|
| When I search for "entity component sytem java", the
| first hit is this project:
| https://github.com/Rubentxu/Entitas-Java which uses a
| "code generator [that] generates classes and methods for
| you, so you can focus on getting the job done. Radically
| reduce the amount of code you have to write and improve
| readability. It makes more of a sea code less prone to
| errors to ensure the best performance."
|
| Sure, it's probably not as widely used as other
| frameworks in other languages. Java isn't necessarily the
| first thing on people's mind when it comes to
| implementing AAA games.
|
| > and those that do, usually silo themselves from the
| ecosystem.
|
| You have not shown this. And if you mean that your C
| macro implementations do this -- fine. But you still seem
| to find them useful enough to keep using them?
|
| > Yes, if you like Java, you can definitely implement a
| Java compiler/interpreter in C and get everything you
| like about Java using C.
|
| _That_ is a Turing equivalence argument. But using a
| very simple code generator for a very simple problem is
| not the same thing. Given that Java syntax is very
| similar to C, I wouldn 't be surprised if your existing
| macros could be used 1:1, with the hardest part being
| getting a Java build system to call the C preprocessor on
| a Java file. Not quite the same thing as implementing an
| interpreter.
|
| (Also, as far as I'm concerned this was never about Java
| per se.)
| beagle3 wrote:
| I appreciate it too, and I don't believe I am.
|
| I wasn't aware it is now an integrated part of the JDK,
| but it doesn't change much; I've used ANTLR decades ago
| (and YACC even more decades ago) which is ... essentially
| the same thing, except Java has now standardized and
| simplified it so that you don't need to fiddle with your
| Ant or Maven or Makefile.
|
| > Sure, it's probably not as widely used as other
| frameworks in other languages.
|
| I've been doing professional work for over 30 years now,
| so I think I have some perspective, which may of course
| be different than yours. Code generators and processors
| have always had their niches, but that's what they are -
| niches - and they are quite small.
|
| People used to use YACC, or ANTLR or Bison or Lemon or
| whatever your favorite parsing tool is. That's because
| they save an enormous amount of work in most use cases
| where they are applied. But they also come with great
| costs: Hard to debug when things go wrong, but most
| importantly - inflexible. GCC and many, many other
| projects move off parser generators at some point in
| their life for flexibility reasons.
|
| Qt has MOC, which was essential in older C++ and is now
| IIRC optional because modern C++ has mostly caught up. It
| is widely hated among Qt users, though it did offer
| significant advantages. But I'm not aware of any project
| that used MOC except to interface with Qt - the boundary
| is always evident.
|
| > You have not shown this. And if you mean that your C
| macro implementations do this -- fine. But you still seem
| to find them useful enough to keep using them?
|
| How could I "show" this? It is obviously predicated on
| the projects I've been involved with, which - I noted -
| have not been Java in the last ten years. It's not like
| it's something that can be "shown", except in some
| statistical sense, the validity of which we will argue
| forever.
|
| And yes, people shy away from macro heavy C code
| (whenever J source code or A source code gets posted on
| HN, people are less than happy, e.g.
| https://news.ycombinator.com/item?id=8533843 ; the whole
| J system looks like that ).
|
| I use macro heavy code only when I don't have to share it
| with others, for that reason.
|
| > But using a very simple code generator for a very
| simple problem is not the same thing.
|
| Well, perhaps we're not thinking of the same level of
| simple - e.g. c++ template meta programming, which I
| first used in 1995 is built in, and (literally) turing
| complete, so it can do anything, but is far from "simple"
| - not to actually write, not to use, and definitely not
| to debug.
|
| What I had in mind was things like
| https://github.com/Hirrolot/poica - It uses C macros to
| do incredible magic. All it needs is one #include and it
| gives C introspection, oop and a few other things. Is it
| simple? I wouldn't say that. But more importantly, is it
| C ? I wouldn't say that, and so does the guy who made it.
| It can be used in a C program, yes. It can e.g. go
| further and define comparators for you so you can use
| qsort() on the new types it helps you define. But it's
| not really C because when you try to integrate it with an
| existing C codebase, you'll find that you keep having to
| translate things at every boundary - which is quite
| likely to happen with a Java SoA library.
|
| I think my point is that languages in common use (Java,
| C, C#, ...), despite offering the facilities to locally
| solve the issues described in the original article
| (locality of reference mostly), they do not practically
| allow these solutions to become widespread or common
| because they are not part of the core, and as a result
| you lose all benefits on library boundaries.
| theamk wrote:
| The code you to actually use it is pretty dangerous - you
| need to keep the index and "main pointer" in separate
| variables, but always use them together. And indexes are all
| plain untyped integers, so no compiler will warn you if you
| use wrong index variable. And GC is no longer thing, so
| "write after free" and friends come back.
|
| Going to column arrays in high-level languages brings back
| many of the C bugs that we really hoped to forget.
| speedplane wrote:
| > Instead, we should be using game-style "data oriented
| programming" a.k.a. "column databases" for a much higher
| performance.
|
| This makes logical sense, but I don't buy it in practice. Most
| of the heavy data reads are handled by databases, which do
| optimize for this stuff. I just doubt that, in most software, a
| significant amount of software performance issues are a result
| of poor memory alignment of data structures.
| gumby wrote:
| When your datasets are quite large a sql database becomes an
| absurd bottleneck. Think of weather or aerodynamics
| simulations.
| nine_k wrote:
| Cache misses can lead to _major_ slowdowns.
|
| Everything depends on the access patterns in critical loops.
| If you need most of the fields of a struct in each iteration,
| the classic way is beneficial. If you need a narrow subset (a
| "column") of fields in a large collection of objects,
| splitting objects into these columns speeds things up.
| tpxl wrote:
| The most upvoted stackoverflow question is about branch
| prediction (which is caching in a way):
|
| https://stackoverflow.com/questions/11227809/why-is-
| processi...
| aparsons wrote:
| What do you mean by game-style here? I'm very intrigued. Any
| place to read more about these?
| foota wrote:
| The keywords here are things like data oriented or
| "structures of arrays". Some examples are unity ECS or
| amethyst and bevy in Rust language.
| astrange wrote:
| It's called entity-component systems in games. Note that OOP
| was designed for simulations but games, the only kind of
| simulation people like using, don't use OOP.
| scambier wrote:
| It's "Entity Component System". The "system" is part of the
| pattern name.
| astrobe_ wrote:
| I think the more accurate references is "Parallel arrays",
| aka "Structure of Arrays" (SoA) [1]
|
| The amusing thing is that SoA is something you could see in
| old languages that didn't have data structures, or the code
| of newbies that don't know data structures.
|
| [1] https://en.wikipedia.org/wiki/Parallel_array
| BlueTemplar wrote:
| Plenty of games use OOP (C++ !). And ECS is just relational
| OOP.
| CodeArtisan wrote:
| https://gameprogrammingpatterns.com/component.html
|
| https://gameprogrammingpatterns.com/data-locality.html
| elcapitan wrote:
| Look for "Data oriented design" or "Structs of arrays".
|
| Here's a talk on the former by Mike Acton:
| https://www.youtube.com/watch?v=rX0ItVEVjHc
|
| On the latter, there's Jai, a new programming language for
| games (WIP, unpublished) by Jonathan Blow, who has been very
| public in documenting the process creating it, and which is
| centered around such concepts. There is some unofficial
| documentation of the ideas on SoA vs AoS and how the language
| can help switching between the two, e.g. here: https://pixeld
| roid.com/jailang/overview/Features/SOA/#/overv... and here: h
| ttps://github.com/BSVino/JaiPrimer/blob/master/JaiPrimer.md..
| .
|
| Edit: Just realized that the articale even mentions Jai
| towards the end.
| loopz wrote:
| Gaming has always been pushing boundaries of what's possible
| performance-wise. For high-throuhput data loops, there's
| already established patterns used by gamedevs that maximize
| use of cache and cpu/gpu.
| jhgb wrote:
| > So we should not be using traditional struct/class-like
| programming model, where we put all properties of an object
| next to each other. Instead, we should be using game-style
| "data oriented programming" a.k.a. "column databases" for a
| much higher performance.
|
| Something like DataDraw (http://datadraw.sourceforge.net/) does
| with C?
|
| > However, most modern languages (C, C++, Python, Java, etc..)
| are not making it easy
|
| Funnily enough, it was yesterday when I though about how cool
| it would be to make an implementation of Oberon-07
| (http://oberon07.com ,
| https://people.inf.ethz.ch/wirth/ProjectOberon/index.html - a
| simple-enough language to experiment with in various ways) that
| would use something like this as its in-memory data
| representation.
|
| It came up as a consequence of me wanting to have an
| implementation of Oberon-07 for WebAssembly, and realizing that
| the necessity of "restarting" the whole binary at any code
| change (necessitated by how WebAssembly works) also allows for
| some liberties in data representation (for example running out
| of compressed IDs for instances of a type could be handled in
| exactly the same way as changing the code of the running
| program - by generating a new binary with expanded space for
| these IDs and restarting it using the serialized old heap).
| oever wrote:
| If memory controller could be given a mask for what bits or bytes
| are wanted, then the memory overhead could be reduced without
| changing the representation in the programming language.
|
| At that point the compiler can optimize memory access by
| generating the mask.
| incrudible wrote:
| If you had the opportunity to change the entire cache hierarchy
| at a hardware level and make all the compilers support that,
| you could do a lot of things.
|
| You don't. You control the software.
| alfiedotwtf wrote:
| > While not having to think about the memory is going to make you
| more productive, that productivity comes at a heavy price.
|
| I highly recommend "Code Optimization: Effective Memory Usage" if
| you want to think more about these problems
| Gibbon1 wrote:
| I'm late to the party but his table is missing an entry
| Event Latency Scaled Latency Network Access
| 50-200ms 5-20 years.
| rusanu wrote:
| There are several versions available of Scott Meyers' CPU Caches
| and Why You care talk:
|
| - https://vimeo.com/97337258 -
| https://www.aristeia.com/TalkNotes/PDXCodeCamp2010.pdf
| elcapitan wrote:
| > The programming languages listed above are all over 20 years
| old and their initial design decisions, like Python's global
| interpreter lock or Java's everything is an object mindset, no
| longer make any sense today
|
| I wonder what general purpose languages (as the mentioned Jai is
| meant for games primarily) will be the best fit for modern
| architectures, besides obvious things like doing it in C and just
| doing everything manually to fit the machine nicely.
| dnautics wrote:
| > I wonder what general purpose languages
|
| I'm looking forward to zig. The author is taking a lot of care
| and effort to make sure the content works on legacy systems as
| well as modern systems, so I think the level of flexibility
| that entails will also mean the flexibility to adapt to the
| future.
|
| Also, IIRC zig has an AOS to SOA transformation in its stdlib?
| In any case, they have been refactoring the language parser to
| use SOA to great effect:
|
| https://github.com/ziglang/zig/pull/7920
| beagle3 wrote:
| It's the APL/J/K/Fortran model. It fits modern CPU and memory
| architectures, it fits GPUs, and slow external storage.
|
| And rather surprisingly, it always has, even when memory
| latency was 1 cycle and there was no prefetch (for which you
| have to go back to the late 70s). The first APL implementation
| is from 1962 or so, first Fortran is 1955 or so.
|
| R and Numpy use it to deliver their speed while still providing
| high level semantics.
| bloaf wrote:
| I too suspect "data oriented programming" is just array
| programming rebranding itself.
| NohatCoder wrote:
| I don't see any particular reason why Jai shouldn't be usable
| for other stuff than games, there are no baked-in assumption
| about any gamey nature of programs.
| mvanaltvorst wrote:
| "Programming languages are old, therefore they will never take
| advantage of our hardware." Have you looked at how compilers have
| changed the past 30 years? How they take advantage of SIMD? AVX?
| The LLVM-revolution? It's delusional to think compilers will
| never be smart enough to vectorise object-oriented code, which is
| the main gripe of this article.
| astrange wrote:
| This article is about transforming memory layouts, which
| compilers don't do. GCC has some really really basic support
| for it which never works.
|
| Autovectorizing doesn't transform memory layouts, which is part
| of the reason it doesn't work that well either.
| moonchild wrote:
| The sad part is chris lattner (llvm author)'s phd
| dissertation was on that exact topic. And yet nothing seems
| to have come of it since despite llvm's hegemony.
| (https://llvm.org/pubs/2005-05-04-LattnerPHDThesis.html)
| SuchAnonMuchWow wrote:
| No, the main gripe of this article is data locality, not
| vectorization. Those two are completely orthogonal and
| complementary: you can have data locality without vectorization
| and vectorization without data locality, and both bring
| performance improvements. But vectorization bring a constant
| speedup (from x2 to maybe x16 depending on the ISA of the
| processor), but data locality can improve performance much more
| because memory accesses are thousands of times slower than
| processor cycles.
|
| But if the semantic of your programming languages doesn't allow
| your compiler to change the layout of your data in memory, then
| there is almost nothing the compiler can do to improve data
| locality in your application.
| barrkel wrote:
| Struct of arrays layouts does put you in a good position to
| apply vectorization techniques though. The article is rather
| more about columnar vs row formats for program entities than
| it is about locality generally. Columnar databases often use
| vectorized evaluation strategies because it's low-hanging
| fruit.
| zaphar wrote:
| The impetus for using a columnar format is precisely to get
| data locality. It is absolutely about data locality
| generally. You wouldn't take his approach at all if you
| didn't get the locality with it's associated performance
| improvements. It would be strictly more work for no gain.
| Locality is the entire point.
| beagle3 wrote:
| > So far, the only programming language I know of that supports
| this type of crazy data transformations is JAI,
|
| As far as I know JAI is indeed the only language that explicitly
| lets you switch between AoS and SoA with one bit, but the APL
| family of languages - (APL, J, K, Shakti, and a couple more) has
| basically - for 60 years no - taken the "data oriented" SoA
| approach for storage, and provides the language support that
| makes it as easy to use as the "object oriented approach" AoS.
| (For some definition of "as easy as" - the languages themselves
| are generally not considered easy to use, but within the
| language, treating things in either way is straightforward, at
| most a "flip" away, but usually not even that is needed).
|
| Additionally, Nim macros (and I suspect Rust and D as well) allow
| this to be a library-level thing as well. Lisp does too, of
| course -- but Nim/Rust/D are much closer to the Algol family-and-
| friends list given in the article.
| mnembrini wrote:
| D approach: https://maikklein.github.io/soa-d/
| celrod wrote:
| ISPC also has first class support for "(array of) structure of
| arrays", see: https://ispc.github.io/ispc.html#structure-of-
| array-types
|
| For example: soa<8> Point pts[...];
|
| declares an array of struct of arrays, where each inner struct
| array contains 8 elements. This would have the advantage of
| playing well with the streaming prefetcher if you're working
| with all fields of the `Point` type, and allowing the compiler
| to use and increment a single pointer for loading/storing,
| accessing each field with a small compile time offset (that can
| get folded into addressing calculations, making them
| essentially free).
|
| Julia's StructArrays package is also very convenient:
| https://github.com/JuliaArrays/StructArrays.jl
| Tyr42 wrote:
| I also think that it's a shame he didn't bring up NumPy &
| Pandas, or R. He's just created a data frame and then
| complained that there's no functions to sort it, but we do have
| those. They are here. ants = pd.DataFrame({
| "name": ["bob", "alice", "carol"], "color":["red",
| "blue", "red"], "age":[1.1, 0.5, 1.2],
| "warrior":[True, False, True]}) # Or read_csv to get
| the data in. # Count number of warriors. True =>
| 1, False => 0 ants['warrior'].sum() # Returns 2
| # Count old red ants ((ants.color == "red") & (ants.age
| > 1.0)).sum() # 2 again ants.sort_values(by="age")
|
| I think Pandas and NumPy should be up there as making the
| language easy to use in a SoA way.
| CornCobs wrote:
| Yeap. I think Pandas could and should see a lot more use
| outside data science as performant in-memory data storage in
| Python.
|
| The best thing is? Most data scientists don't even care about
| SoA vs AoS - tabular structure is easy to grok, easy to use
| AND performant by default!
| davidkunz wrote:
| By coincidence, there is a new Nim blog post demonstrating
| exactly that. With the same ant example. https://nim-
| lang.org/blog/2021/05/01/this-month-with-nim.htm...
| masklinn wrote:
| > Additionally, Nim macros (and I suspect Rust and D as well)
|
| Aye somebody posted their SoA library on /r/rust just a few
| days back (https://crates.io/crates/soa_derive), and I don't
| think it was the first such.
| jamesbrock wrote:
| > What if a programming language would provide us with a
| structure that would act like an array of structs, but internally
| it would really behave like a struct of arrays? We could program
| in the typical object oriented way that is convenient for humans,
| while still enjoying in great performance due to playing nice
| with the hardware.
|
| > So far, the only programming language I know of that supports
| this type of crazy data transformations is JAI,
|
| Haskell supports this type of data transformation.
|
| http://hackage.haskell.org/package/vector-0.12.3.0/docs/Data...
|
| > unboxed vectors of pairs are represented as pairs of unboxed
| vectors.
| CyberDildonics wrote:
| Haskell uses linked lists everywhere, so any transformation
| like this likely does nothing for speed.
| astrange wrote:
| Haskell uses linked lists as a metaphor for generators (since
| it's a lazy language), which is a strange decision since
| linked lists are a bad data structure, so you have to write
| the program to avoid accidentally ever doing what it says it
| does.
| dnautics wrote:
| > linked lists are a bad data structure
|
| Why is that? They're perfectly fine for most purposes, and
| have the advantage that when you pass one across a function
| boundary the callee can play with it or modify it or do
| whatever the hell they want and when the callee returns,
| the caller's linked list is untouched. That's an
| _incredibly_ valuable feature for correctness and safety.
| astrange wrote:
| They have poor cache performance when you want to iterate
| over them, and more size overhead, because there's one
| pointer lookup per item. Also, the common definition of a
| linked list has the list responsible for allocating each
| item, meaning it can't be a member of more than one list
| at once. The actual useful version of this is called
| instrusive linked lists, which isn't the one taught.
|
| > That's an incredibly valuable feature for correctness
| and safety.
|
| Are you thinking of something else? That's an effect of
| call by value, but linked lists actually aren't good at
| that and you have to copy them to pass them around. You
| need something like these to make it efficient:
|
| https://en.wikipedia.org/wiki/Finger_tree
|
| https://en.wikipedia.org/wiki/Read-copy-update
| tom_mellior wrote:
| > Are you thinking of something else? That's an effect of
| call by value, but linked lists actually aren't good at
| that and you have to copy them to pass them around.
|
| Are _you_ thinking of something else? Haskell (and all
| other ML-family languages, and Lisps) lists are passed
| around by copying a single pointer to the head element.
| Different lists can share tail elements, i.e., elements
| can be members of more than one list.
|
| All this is in contrast to Java-style linked lists, where
| there is a list object that controls its elements.
| creata wrote:
| > Are you thinking of something else?
|
| I think the point is to contrast them with dynamic arrays
| --- linked lists, unlike arrays, are persistent data
| structures, which does make them easier to reason about.
|
| > Also, the common definition of a linked list has the
| list responsible for allocating each item
|
| I'm not sure how this is relevant in the context of
| Haskell, given that it's garbage collected.
| tom_mellior wrote:
| The given link describes unboxed vectors. This is Haskell-
| speak for "actual array of actual machine numbers".
| dhsysusbsjsi wrote:
| It's true that compilers won't optimise memory layout but I would
| argue this is an issue for only a subset of the software
| engineering industry. More important is robust bug-free code with
| separation of concerns which these tools allow. Remember the
| first rule of optimisation: "don't". The second rule of
| optimisation: "Don't...yet".
| thrwaeasddsaf wrote:
| Those two are followed by a third rule: "whoops it's too late
| now, we'd have to rearchitect and rewrite everything and we
| can't justify the cost"
| jefftk wrote:
| It's not a fun language to work in, but SQL does a lot of what
| the author is asking for.
| 0-_-0 wrote:
| There is a Nim library inspired by this blog post that simulates
| array-of-structs with struct-of-arrays (implemented through
| macros):
|
| https://github.com/liquidev/datarray
| viktorcode wrote:
| Jai can't come out fast enough. Don't think for one instance that
| the language is only for writing games; it will serve equally
| well for writing any kind of software where the performance is
| nice to have.
| gumby wrote:
| > What if a programming language would provide us with a
| structure that would act like an array of structs, but internally
| it would really behave like a struct of arrays?
|
| I do this all the time (well, it's not uncommon) in C++. The nice
| thing is I can mix the two with a bit of manual glue so callers
| don't even have to know (though if you are doing this it's often
| worth letting some callers know for the reasons described in this
| post.
| jvanderbot wrote:
| Did you also notice how the need for interface and type
| abstractions goes away? If you have a function that sum()'s a
| list of numbers, you can use that to sum() any of the Ant's
| members by passing a ref to the list. Otherwise you have to write
| AntSummer classes that take Ant (and all the children of Ant's
| class).
|
| It does occur to me that there's a certain type of programmer for
| whom this is obvious. And that type of programmer was not using
| classes anyway for the reasons above. For that programmer,
| objects are a massively overused hammer in software design. I
| know those types, and they're very good. It might be there's too
| much brainpower wasted in designing object hierarchies that could
| go to exploring functionality or producing value. Or it might be
| coincidence.
|
| I'm not really that guy necessarily, but for simply reasons of
| communicating and comprehending designs I always thought objects
| more than a few members were just plain wasteful. There just
| never seemed to be a reason to have all those mutable hidden
| member variables unless it was to abstract something like a data
| structure.
| Const-me wrote:
| Disagree about garbage collected runtimes.
|
| A lot of widely used software is written in C or C++ and uses
| malloc/free extensively (C++ new/delete is mostly a wrapper
| around it). This results in memory layout worse than an
| equivalent managed heap would be. Happens because the memory
| allocated on C heap is immovable, while garbage collectors may
| move data around to defragment the heap.
| cma wrote:
| Data-oriented designs like ECS implementations often use
| generational indices, which can support relocation and
| defragmentation.
| moonchild wrote:
| Actually...https://arxiv.org/pdf/1902.04738.pdf
|
| On a more serious note, most c programs I see don't do very
| much of that, preferring to allocate fixed-size buffers on the
| stack. Additionally, most GC'd languages that aren't java have
| crappy GCs, and those GC'd languages that _are_ java lack value
| types. This harms spatial locality quite a bit, and compaction
| can 't fully compensate for that.
|
| They are working on adding value types to the jvm, though; it
| will be very interesting to see how performance changes. (I
| expect not very much in practice--those projects for which
| performance was important were already using whatever tricks
| they needed to--but perhaps it will improve the ergonomics of
| performant java.)
| Const-me wrote:
| > preferring to allocate fixed-size buffers on the stack
|
| When working set is measured in megabytes, it fits in L3
| cache of modern CPUs. Memory layout is not too important for
| these programs.
|
| > most GC'd languages that aren't java have crappy GCs
|
| C# is good too. It also has value types, native memory spans,
| SIMD intrinsics, and stackalloc.
| PixelOfDeath wrote:
| Don't forget that good cache locality also can cause data
| being pulled into cache that the prefetcher did know
| nothing about.
|
| I can create you a shitty linked list that fits perfectly
| in L3, but still has terrible cold cache performance
| because each individual cacheline has to be pulled in one
| by one.
| Const-me wrote:
| Indeed, there're edge cases. Still, how many people using
| linked lists on the stack?
| vips7L wrote:
| Doesn't D have value types via structs since by default
| they're stack allocated and copied? Though I'm not sure how
| well it's GC performs when you do allocate on the heap.
| jeff-davis wrote:
| "What if a programming language would provide us with a structure
| that would act like an array of structs, but internally it would
| really behave like a struct of arrays?"
|
| What about SQL when using a columnar data store?
| rualca wrote:
| An array of structs is already a struct of arrays if you're
| willing to do some offset math.
| incrudible wrote:
| That would make the whole thing moot. The crucial difference
| between AOS and SOA is that the "physical" memory layout is
| different:
|
| AOS: [xyzw][xyzw][xyzw][xyzw]
|
| vs
|
| SOA: [xxxx][yyyy][zzzz][wwww]
|
| If you have such a layout, a SIMD instruction touching 4
| packed words has quite different behavior. Arguably, SIMD is
| pointless without SOA or AOSOA.
| rualca wrote:
| > SOA: [xxxx][yyyy][zzzz][wwww]
|
| That difference only occurs if, for some random reason, you
| came about what's the address of element zero of that
| array, or if for any even rarer reason you have a hard
| requirement of when you cease to process x and start to
| process y, and you find a single memory jump prohibitively
| expensive.
|
| Meanwhile, AOS is already by definition [xxxx] and [yyyy]
| and [zzzz] and [wwww], given the remaining members of the
| struct are treated as padding.
| incrudible wrote:
| > Meanwhile, AOS is already by definition [xxxx] and
| [yyyy] and [zzzz] and [wwww]
|
| There must be a misunderstanding here. AOS is by
| definition [xyzw][xyzw](...), the (C/C++) compiler has no
| leeway in rearranging this. I'm also assuming that
| there's no padding necessary, in either case.
|
| Perhaps my way of notation is confusing, in that case I'm
| going to refer you to this page, which has code examples:
|
| https://software.intel.com/content/www/us/en/develop/arti
| cle...
| BlueTemplar wrote:
| At some point there's an assumption that OOP and DOD are
| incompatible, while they are actually orthogonal.
| zmmmmm wrote:
| It's interesting that Java/JVM is both one of the worst cases and
| best here. When you work only with arrays of primitives its
| performance is shockingly good and aligns well with the needs for
| locality. Hopefully future improvements extend that to include
| structs/records and then it may actually be quite a powerful
| platform in this regard.
| TazeTSchnitzel wrote:
| The metaprogramming available in languages like Rust and C++
| could maybe be leveraged to make the struct-of-arrays model
| easier. Wouldn't be the same as native support, though.
| adgjlsfhk1 wrote:
| Julia has a package that provides incredibility easy to use
| SoA. https://github.com/JuliaArrays/StructArrays.jl Julia's
| metaprogramming and multiple dispatch mean that it can use user
| structs fully transparently. Everything just works.
| mhh__ wrote:
| I've seen this done in D fairly easily, almost definitely
| closed source unfortunately.
| PixelOfDeath wrote:
| There is some, lets call them experimental, C++ libraries for
| that. But C++ not having static reflection is the main show
| stopper on that frontier.
| f00zz wrote:
| > There is some, lets call them experimental, C++ libraries
| for that.
|
| Could you provide some links?
| PixelOfDeath wrote:
| https://github.com/Yamahari/struct_array
|
| It uses C++20 features and macros. The macros limit it to a
| POD count of 8.
| f00zz wrote:
| Thanks!
| nine_k wrote:
| I can remember the columnar approach used in Fortran back in the
| day. Fortran did not have records, only scalar arrays, so a
| natural way to represent a bunch of "objects" was to have a bunch
| of arrays for each property value.
|
| IDK if such prior art is any helpful today, though :)
| disgruntledphd2 wrote:
| This approach is _still_ used (conceptually, at least) in
| statistical computing. NumPy and R are good examples of this
| approach, and also have solutions for the indexing problems
| outlined in the article.
|
| That being said, statistical computing is at the mercy of the
| high cost of matrix multiplication.
|
| I think that you can alter the order of arrays in numpy, and
| examine the performance difference.
| grayclhn wrote:
| I think the desired functionality is closer to Pandas than
| Numpy -- you want coordinated arrays of different data types,
| not higher-dimension arrays of the same data type.
|
| R and Pandas aren't exactly known for their outstanding
| native performance (they're generally fast _after_ converting
| dataframes to arrays and outsourcing to C or Fortran) so
| they're great demonstrations of the syntax for some of these
| indexing problems but leave a lot to be desired.
| cozzyd wrote:
| ROOT TTrees will "split" class members to effectively
| transform them into AoS although this is mostly for IO
| efficiency.
| offtop5 wrote:
| Back in the real world the best code is code that ships.
|
| Occasionally I find myself doing something stupid ( if you use
| Unity and try to do advance distance calculation in Update your
| going to have a bad time ) , but usually I never have to
| optimize. Modern computers might as well be magic
| jmull wrote:
| There's a nice introduction to a data-oriented approach in here,
| but it's buried in some junk.
|
| E.g., I don't think a lot of developers believe compilers are
| magic optimizers. And programming languages, including the ones
| referenced, have changed quite a bit over the last 20 years. A
| strong enough case for general data-oriented features would cause
| at least some of them to support it.
|
| Also, a great amount of code should be written for
| maintainability -- that is, to be read and easily understood by a
| future developer without special insight -- rather the
| performance. Talk of "software cultists" is silly and shows how
| poorly the author understands the issues driving software
| development approaches.
|
| Not to mention, a performance analysis of software systems that
| stops at main memory is about only a subset of software (one that
| has a pretty thin slice of overlap with "enterprise" software,
| where your bottlenecks are overwhelmingly IO and DB).
| jolux wrote:
| To me, the goal of language implementations should be to make
| the most maintainable code also the fastest code. I shouldn't
| have to worry much about the performance characteristics of my
| software outside of general concerns about algorithmic
| complexity.
| [deleted]
| secondcoming wrote:
| That all depends on what type of software you write. I don't
| think there'll ever be a language that ticks every
| requirement box.
| jolux wrote:
| You can't get all the way there but it's a North Star, I
| think.
| maydup-nem wrote:
| The first approach creates a list, which means cheap
| insert/delete. But it's not a fair comparison, as the second
| approach doesn't let you do that. For that, you would need to
| allocate an array of objects in the first approach instead of a
| list.
| dragontamer wrote:
| While this article starts to discuss the low level details that
| underlie our code, I feel like its incomplete.
|
| * Latency hiding -- CPUs work extremely hard on latency hiding. A
| lot of those OOP-indirect calls will be branch predicted by the
| CPU in the simple cases (ex: if one object-type is more common
| than others, then those indirect branches will be branch
| predicted correctly in most situations).
|
| There are other bits of latency hiding: Hyperthreads, Out-of-
| order execution, pipelines.
|
| * Cache: L1, L2, and L3 caches can grossly mitigate a lot of the
| problems. While CPUs today are far faster than DDR4 RAM, L1 cache
| scales perfectly with the CPU. If you can keep your code+data
| under 64kB, you'll always be executing in L1 cache and basically
| get the full benefits.
|
| If 64kB is too restrictive, then you have 256kB or 512kB (L2
| cache), or 8MB (L3 cache), both of which scale with the CPU.
|
| ------------
|
| SIMD and Multicore goes backwards IMO. It turns out that the
| 1970s and 1980s were filled with very intelligent programmers
| working on extremely sophisticated supercomputers. I enjoy going
| back to the 1980s and reading Connection Machine articles on
| C-Star (a parallel language that's arguably the precursor to
| CUDA), or Star-Lisp.
|
| --------
|
| If you write multicore software, then SMT / Hyperthreads are very
| effective at latency hiding. Running two threads on one core (or
| 4-threads or 8-threads, in the case of some IBM processors)
| allows your cores to "find something else to do" during those
| long latency periods where your CPU is traversing memory and
| waiting for RAM to respond.
|
| Working extremely hard to optimize just a single thread seems
| somewhat counterproductive. Its often easier to write "less
| optimized" multicore software than highly optimized single-core
| software.
|
| And SMT / hyperthreads will converge those onto a single core
| ANYWAY. So might as well take advantage of that feature.
| lordnacho wrote:
| Good article. My own thinking when coding performance is also
| towards data oriented approaches. For example Bevy in Rust. If
| stuff needs to be fast, it needs to be in cache. To do that, have
| everything nicely packed so you only ask for a chunk as often as
| you need. Then when you need it, it's already there.
|
| Thing about old fashioned OO is it's often fine enough for your
| run-of-the-mill CRUD app. If you look at the latency chart he
| provides, there's another level to it: going to a database
| server. If you're gonna do that, you're not really in the speed
| game anyway, so why not make it simple? Just write it in the most
| uncomplicated way the language allows, and that's it. Waiting for
| the DB will take way longer than filtering the ants, forget all
| the optimizations.
|
| Edit:
|
| Forgot to mention with ECS, you have a different organizing
| principle. It can be quite enlightening to work with "systems
| that operate on data" as opposed to OO where you have the data
| declared next to the functions that mutate it. It can add clarity
| for certain problems. Somehow over the years I've found that the
| animals/cars analogies given in OO tutorials are one of the few
| places that fit well with the model.
|
| If you imagine a game that has characters, relationships, items,
| spells, and so on, it's often not that easy to model as
| encapsulated objects. Does each character have a list of other
| characters that they have a relationship with? What happens when
| you want to change the relationship, or a character casts a spell
| that temporarily changes the relationship? Can easily end up a
| mess, because it's not obvious where such things should live in
| an OO model.
| BlueTemplar wrote:
| You're talking like ECS and OOP are separate, while ECS is
| actually relational OOP :
| https://www.gamedev.net/blogs/entry/2265481-oop-is-dead-long...
| dragonwriter wrote:
| > If you imagine a game that has characters, relationships,
| items, spells, and so on, it's often not that easy to model as
| encapsulated objects.
|
| I...disagree. ECS definitely has efficiency advantages for
| games in terms of support performant implementations, which for
| games is often overwhelmingly critical, but there's well
| understand OO ways to address the modelling issue tou raise.
|
| > Does each character have a list of other characters that they
| have a relationship with?
|
| It probably _acts like it does_ , but it probably doesn't
| maintain it, instead deferring to a separate object that acts
| as a repository of relationships, which are themselves objects,
| which allows you to modify relationships without touching the
| characters at each side (which may be more than two for
| multilateral relationships).
|
| > What happens when you want to change the relationship, or a
| character casts a spell that temporarily changes the
| relationship?
|
| You retrieve and act on the relationship object, which knows
| how to handle modifications.
| megameter wrote:
| That doesn't really encapsulate anything, then. You end up
| with global data wrapped in classes.
| TeMPOraL wrote:
| You always have "global data" in your program - i.e. the
| sum of all it models. The issue is with controlling who can
| see and modify it. Encapsulation is a way to establish that
| control, and it's purpose is to minimize cognitive load on
| the programmer.
|
| The problem with relationship example is that modelling
| this is tricky, and you have to be careful - and that's
| regardless of the programming paradigm used. For instance,
| if you're talking about "a relationship between two
| characters" as an objective, categorical thing (e.g. "A and
| B are friends", "C and D are enemies"), you're thinking
| about a concept that's separate from the characters
| themselves. So you _don 't want_ your characters to have a
| list of other characters, because it splits a single
| concept into two pieces and risks creating inconsistencies
| (A has a "friend" pointer to B, but B doesn't have a
| "friend" pointer to A). On the other hand, character A may
| want to _query_ the list of characters to whom they 're
| related, so you'll want to be able to _generate_ that list
| on the fly.
|
| Thus the OOP design would be: some object that manages all
| the relationships, that object being reachable from every
| character, and able to answer a query "what are all
| relationships for character $X?". An action modifying a
| relationship (say a spell temporarily making A and B
| enemies) would therefore act on that relationship manager
| too - which makes sense, because the action is modifying _a
| relationship_ , a concept that's separate from characters
| themselves.
|
| On the other hand, if you wanted to model relationships
| from the point of view of your characters, i.e. what they
| _think of_ another character, then such relationships stop
| being an independent concept. You 're now modelling the
| subjective perspective - and thus these would belong to
| characters, and in OOP, you'd likely model them as a list
| on the character object.
|
| Whether you're doing classic OOP, plain functional
| programming, data-oriented programming - you still have to
| think about the questions described above in order to pick
| a correct representation for your paradigm. These questions
| are independent from the programming paradigm.
|
| (And to add a further example to the mix, if you're storing
| character data in a relational database, these questions
| boil down to "is 'relationship' many:many, or 1:many?", and
| the answer determines the kind of bridge table you'll use.)
| kstenerud wrote:
| It's mostly a question of hierarchy of data and
| responsibilities. Character objects that administer changing
| relationships are doing too much.
|
| For example, relationships involving "characters",
| "relationships", "items", "spells" etc can be handled by
| objects like "party" and "inventory" and "spell book"
| (glorified lists supporting a few custom named operations that
| basically do the same things) which contain the links to other
| objects. A spell that temporarily replaces your equipment
| simply swaps out your "character's" "inventory" object with
| some magical temporary one, and then swaps the real one back
| again when the "effect" from that "spell's" "invocation" ends.
| After this, adding support for something else like a spell that
| swaps inventory with the victim or steals their equipment would
| be trivial.
|
| Convert this to functional terms and you've basically got a
| hierarchy of data that you operate functions on and generate
| new data to put back into the hierarchy, matching the morphing
| environment. It's all about the data in the end, regardless of
| your source point of view.
| grayclhn wrote:
| > If you look at the latency chart he provides, there's another
| level to it: going to a database server. If you're gonna do
| that, you're not really in the speed game anyway, so why not
| make it simple?
|
| OTOH, this is where I can see the biggest benefits from
| building a language/system around the article's approach. You'd
| really like to execute a lot of the logic on the DB (or some
| other distributed system) which needs to be deeply integrated
| with the DB to be as convenient and expressive as pulling the
| data and operating locally.
| jayd16 wrote:
| I always laugh when people talk about ECS vs OO as if the
| entities, components and systems weren't objects. Its an OO
| pattern.
|
| Even in the most abstract form when entities are just ids, and
| you combine components and systems, you're still going to
| implement the component-system like an object because OO is
| super useful. Using arrays doesn't mean you're not using OO.
| tgaj wrote:
| > If you imagine a game that has characters, relationships,
| items, spells, and so on, it's often not that easy to model as
| encapsulated objects. Does each character have a list of other
| characters that they have a relationship with? What happens
| when you want to change the relationship, or a character casts
| a spell that temporarily changes the relationship? Can easily
| end up a mess, because it's not obvious where such things
| should live in >an OO model.
|
| It's not really that hard. You just have one
| RelationshipManager that every Character have access to via
| simple public methods. OO is not so bad, especially when you
| forget about inheritance and use composition.
| lordnacho wrote:
| But splitting it out into another class is halfway there
| already, no? The relations object doesn't correspond to a
| real domain object anymore. Next step is just to split out
| all the other components.
| dragonwriter wrote:
| > The relations object doesn't correspond to a real domain
| object anymore.
|
| "relationship" is a noun describing a real feature of the
| domain, and thus is a real domain object.
| lordnacho wrote:
| You could say that about anything you construct in your
| model though? Stops being OO if it's not somehow similar
| to an admittedly vague concept of what an object is,
| surely?
|
| I could write the whole thing as an ECS and call all the
| objects systems or components, and then they are nouns in
| the domain?
| jcelerier wrote:
| ECS can definitely be built in an OO fashion.
| dragonwriter wrote:
| > You could say that about anything you construct in your
| model though?
|
| That's kind of the point, yes, model -> nouns -> objects.
|
| > Stops being OO if it's not somehow similar to an
| admittedly vague concept of what an object is, surely?
|
| There's nothing particularly vague about "noun in the
| domain"; linguistic-based modelling certainly isn't the
| be-all and end-all of OOP modelling, but its one of the
| traditional approaches dating back to the early 80s and
| its more sophisticated than thr kind of naive tangible-
| item approach that seems to be set up as a strawman here.
|
| > I could write the whole thing as an ECS and call all
| the objects systems or components, and then they are
| nouns in the domain?
|
| A relationship is a real thing in the domain being
| modelled, "systems" and "components" are (in the sense
| you are using them) not, unless the thing you are
| modelling is a an ECS implementation of a domain. There
| might be some utility for such a second-order model in
| OOP (if the OOP is for a code generator, for instance),
| but it wouldn't be a model of the domain addressed by the
| ECS system, but of the ECS system itself.
| mmis1000 wrote:
| That sounds like a poor man's component if you already
| separated it into another class...? And that data is no
| longer belonging to the instance now.
| meheleventyone wrote:
| OOP based component approaches are really common so it
| shouldn't be surprising.
| dragonwriter wrote:
| > And that data is no longer belonging to the instance now.
|
| It belongs to the _correct_ instance. The noun in the
| domain to which the information logically uniquely belongs
| is a relationship, not a character (of which there is
| typically more than one with different roles in a
| relationship, the number and roles varying by type of
| relationship.)
|
| One problem that textbook OOP examples produce is that they
| tend to favor a heirarchy of physical entities as examples,
| which has the virtue of familiarity but really obscures the
| analysis of nouns in the domain, the most important of
| which in most real domains are the nouns describing
| relationships, activities, and events.
| mmis1000 wrote:
| The same thing can apply to every other property I think?
| probably you can seperate the User::name into a
| PersonIdentityProperty something like that, and remove
| the type limit to allow it to contain any property. Then
| it is basically a ecs now. And that do actually has some
| pros. People can have multi name, you system at least
| works properly with it now(and for free).
| beagle3 wrote:
| Ah ... here we are again.
|
| http://www.paulgraham.com/reesoo.html
|
| OO is not so bad because OO is not well defined.
| beagle3 wrote:
| > Somehow over the years I've found that the animals/cars
| analogies given in OO tutorials are one of the few places that
| fit well with the model.
|
| Yes, and I've never actually needed to implement a cat or a cow
| in any project :)
|
| The other thing for which OO works much better than plain data
| is GUIs - and I think it is not a coincidence that OO
| popularity exploded together with the the coming-of-age of
| GUIs.
|
| The other canonical example of OO - "Shapes" - doesn't actually
| work well at all; It doesn't work better with "plain data", but
| it exposes the fallacies of trying to use OO inheritance to
| model the real world. Every square is a rectangle, so square
| should inherit from rectangle ... but, you can't stretch width
| and height independently in a square, so it's not _really_ a
| rectangle, etc. etc.
| dragonwriter wrote:
| > The other canonical example of OO - "Shapes" - doesn't
| actually work well at all.
|
| Shapes work quite well with OO if you design the heirarchy
| right using is-a relationships, the typically cited problem
| involves applying is-a relationships which apply to immutable
| geometric concept of shapes to mutable representations whose
| mutation contracts don't support the is-a relationship of the
| immutable shapes. This would actually be fine if the
| contracts were written in a way which respects the it's-a
| relationships properly. E.g., the common Circle-Ellipse
| problem goes away if the contract for the mutator for, say,
| the major axis of an Ellipse doesn't specify what the effect
| is on the minor axis. For the base class, the two can then be
| independent with the Circle subclass having them invariably
| equal under mutation.
|
| Alternatively, its not a problem with a Smalltalk like
| "become", in which case an unequally stretched Circle
| _becomes_ an Ellipse with appropriate attributes.
|
| Or, the mutable elements arr restricted to things thst don't
| change the shape like scale and location, and shape
| transforms return a new Shape; if Ellipse's StretchMajorAxis
| returns an Ellipse and so does Circle's, there's no problem,
| either.
| beagle3 wrote:
| I should have added "in C / C++ / C#".
|
| Everything OOP works well in Smalltalk (and most of it does
| in Python), but you pay dearly for that in efficiency -
| which is what this article as about.
| dragonwriter wrote:
| > I should have added "in C / C++ / C#".
|
| The only part of my response that would have effected was
| the aside about Smalltalk-style "become", not the main
| point about the problem being one of constructing an
| inheritance heirarchy based on relations of immutable
| entity and using it for mutable entities with mutation
| contracts that don't observe the same is-a heirarchy.
| divs1210 wrote:
| > The other thing for which OO works much better than plain
| data is GUIs
|
| This cannot be further from my experience. Switching from
| Java/AWT/Swing to ClojureScript/Reagent/Re-frame has been
| suoer liberating.
|
| Every GUI programmer must fiddle with reagent once:
|
| https://reagent-project.github.io/
| BlueTemplar wrote:
| But you're super limited if you're trying to make a GUI
| inside a browser, how is this a good example ?
| iforgotpassword wrote:
| Well, you really picked the worst representative with
| awt/swing. I'd say Qt or even classic VB6/Delphi are great
| examples for OOP gui development.
| MaxBarraclough wrote:
| How about JavaFX? Swing has been old hat for years now.
| iforgotpassword wrote:
| I haven't touched java gui code in almost a decade, but
| didn't they suddenly pull a 180 on JavaFX a few years
| ago?
| MaxBarraclough wrote:
| Not exactly, they moved it out of the core Java standard
| library. The main effect of this seems to have been that
| many developers are now uncertain of whether JavaFX can
| be relied on, but it is still maintained.
| astrange wrote:
| Cocoa has been a great OOP-based UI framework forever.
| The key is that it doesn't use inheritance and has
| messaging
| (https://wiki.c2.com/?AlternateHardAndSoftLayers).
| jcelerier wrote:
| > The key is that it doesn't use inheritance
|
| Cocoa is literally based on inheriting from NSView (which
| itself inherits from NSResponder)
|
| https://developer.apple.com/documentation/appkit/nsview
| astrange wrote:
| That's if you want to make a custom view, which very
| often you don't. Placing default views in a window and
| receiving events/values from them doesn't use
| inheritance, instead using composition and delegate
| patterns.
|
| UIKit lost some of the convenience features like bindings
| when it shrunk to fit on phones, which might be why
| people got annoyed enough to invent entirely new UI
| frameworks.
| jcelerier wrote:
| > That's if you want to make a custom view, which very
| often you don't.
|
| I have never seen any non-trivial software not have
| custom-drawn widgets - grepping for `: NSView` in my ~
| gives me a few thousand matches (and it's not even a mac)
|
| > Placing default views in a window and receiving
| events/values from them doesn't use inheritance, instead
| using composition and delegate patterns.
|
| ... but you can't have composition without at least
| interface inheritance, and in this case you really want
| access to the parent methods & properties of NSView when
| creating your own widgets (e.g. bounds, rects, tooltips,
| etc) so you also want implementation inheritance.
| XorNot wrote:
| I just...I really want to like Clojure, but I cannot get
| past the brackety notation of lisp-likes. Surely there's a
| way to do it where I'm not in bracket hell?
| grzm wrote:
| Use tools like paredit that support structural editing.
| There are tools like this for all major editors.
|
| https://calva.io/paredit/
| slifin wrote:
| OO is a tree and html (UI) on a page is a tree
|
| The problem is when you want to go to page two and want to
| display the same data as page 1 but in a different
| configuration
|
| OO is suddenly terrible because the data is the wrong shape,
| you should hold your data as a graph then derive trees out of
| it to satisfy views
|
| OO is not good for UIs unless you have one page and the
| contents on that page is static and doesn't change it's
| placement outside the statically defined tree shape of OO
| kaba0 wrote:
| OO can be a tree, as well as pretty arbitrary graphs. As
| has been said for over a decade, prefer composition over
| inheritance. There are cases where inheritance is really
| cool, but the main point of OOP is encapsulation. It can
| enforce class invariants.
| beagle3 wrote:
| I Was referring to the lower level GUI world, that in which
| you implement "Button" and "TextField" - OO was, and still
| is, a great win there. The Win32 / Cocoa / Xt level.
|
| But even inside the browser, the fact that even
| div/span/thing is an object, to which you can attach
| listeners, and otherwise apply methods, works rather well -
| I'm not familiar with a better alternative for
| implementation and dynamic control of _this_ layer
| dragonwriter wrote:
| > OO is a tree
|
| Inheritance heirarchies in single-inheritance languages are
| trees. In MI they are DAGs. But neither of those constrains
| _data_ representations, OO can support arbitrary data
| graphs.
|
| > OO is suddenly terrible because the data is the wrong
| shape, you should hold your data as a graph then derive
| trees out of it to satisfy views
|
| How does this make OO terrible? What you describe is
| exactly standard OO GUI practice (specifically, in
| MV<whatever> architecture, the model layer is an arbitrary
| graph representing the data, from which the view is
| derived.)
| TeMPOraL wrote:
| > _The other thing for which OO works much better than plain
| data is GUIs - and I think it is not a coincidence that OO
| popularity exploded together with the the coming-of-age of
| GUIs._
|
| Yeah, and I think the reason has nothing to do with objects
| per se. I've recently been coming to conclusion that the one
| thing that makes OOP last is that it's neatly packaging an
| important feature that other programming paradigms struggle
| with or ignore: _late binding_ (aka. dynamic linking).
|
| Is it better to write a class Foo with a method Bar(stuffs),
| or a struct Foo and a function Bar(Foo&, stuffs)? I don't
| care. They're literally the same thing.
|
| But say I want to change the system so that, in some cases,
| it calls Baz instead of Bar - but I don't want to rip the
| system apart and change every call site. With OOP, I just
| make a class Quux inheriting from Foo, have it override
| Bar(stuffs) to do Baz code, and shove a Quux object into the
| system. Done. Dynamic linking ensure that my new
| implementation gets called for Quux objects.
|
| Meanwhile, in most programming languages, you can't pull that
| same trick with a struct. Not without having to manually
| reimplement dynamic dispatch - at which point you may as well
| use an OOP language.
|
| Dynamic dispatch is a super useful thing to have. You can
| minimize its use - and probably should, for performance
| reasons - but when your language doesn't support it as a
| built-in feature, these few cases in which you need it get
| very painful to write.
| ric2b wrote:
| > But say I want to change the system so that, in some
| cases, it calls Baz instead of Bar - but I don't want to
| rip the system apart and change every call site. With OOP,
| I just make a class Quux inheriting from Foo, have it
| override Bar(stuffs) to do Baz code, and shove a Quux
| object into the system. Done. Dynamic linking ensure that
| my new implementation gets called for Quux objects.
|
| Functional programming does this with pattern matching, it
| is just as easy and avoids the dangers of sub-classing.
| lowercase1 wrote:
| The source of this capability is hidden in that you had to
| pass in a Foo to the call site.
|
| If you had done similarly in the struct example by passing
| in the function Bar to the caller then you could achieve
| similar functionality by shoving in a Baz function instead.
| TeMPOraL wrote:
| Yeah, but that's the thing: in case of dynamic dispatch
| in OOP, the function is tied to the argument you're
| passing. In a typical OOP language, your object carries
| an extra pointer that's hidden from your view - a pointer
| to a table of pointers to functions, specific for that
| object's class. Dynamic dispatch goes through that
| pointer on the object.
|
| Doing what you describe with a struct would require _at
| least_ keeping explicit function pointers on the
| structure; the OOP mechanism described above is a
| generalization of that.
|
| (There are other, neater approach too, allowing late
| binding dispatched on the types of more than one argument
| - see e.g. methods in Common Lisp.)
| [deleted]
| acje wrote:
| I'm on thin ice here. A systems complexity can be measured in
| the number of tops it has. As in different ways to see it as
| hierarchical.
|
| Could it be that OO favors systems with one top? The tomato
| example comes to mind. Is it a fruit or vegetables? According
| to American law it was classified as vegetable at some point
| to meet a political need to limit imports. Of course
| botanically it's a fruit but usually perceived as a
| vegetable. So three tops identified just in this example.
| Depends on context; import, science or consumers.
| dnautics wrote:
| > The other thing for which OO works much better than plain
| data is GUIs
|
| I think the absolute dominance of React and its functional
| style (especially among juniors) is pretty good evidence
| _against_ this belief.
| kaba0 wrote:
| It is pretty much an MVC separation that was mainstream in
| OOP-based GUI frameworks since the 90s at least.
|
| But instead of "state", you have a model, which uses the
| Observable pattern to notify the view of changes.
| Tomte wrote:
| > as opposed to OO where you have the data declared next to the
| functions that mutate it.
|
| In the popular OO languages. As usual, the Common Lisp Object
| System is always worth a look. You have classes that
| encapsulate data, and then you have "free" generic functions
| (well, methods that implement those generic functions) that
| operate on that data.
| kaba0 wrote:
| How does that enforce class invariants?
| rhn_mk1 wrote:
| To elaborate on Bevy: the data-oriented approach used is called
| an Entity Component System.
| meheleventyone wrote:
| And super importantly an ECS is not necessarily data-
| oriented.
| jupp0r wrote:
| The implicit premise of the article seems to be that all software
| has to be heavily optimized. This is completely wrong. All the
| languages mentioned in the article are still around because it
| doesn't matter how performant 99% of code is. For the 1%, we can
| think about cache misses, SIMD and data parallel approaches. In
| my experience this is totally possible and not too hard, but has
| the enormous downside of making that code hard to reason about.
| You are also optimizing for today's hardware, which means that it
| might not run that great on new hardware in 20 years. This means
| that somebody has to go in and rewrite that hard to understand
| optimized code. It's really a tradeoff between the cost of that
| and the benefits from optimization.
|
| Some of the future costs can be mitigated by using a few
| techniques from the start. One good idea is to keep an vanilla
| unoptimized version of the part that you are optimizing around as
| a reference implementation. This will help understanding what the
| optimized code does and can be used as a stopgap to support new
| hardware. It can also be a nice baseline to compare optimizations
| against.
|
| The beauty of compilers is that they do a decent job of
| optimizing the 99% of code that would otherwise never be
| optimized, at 0 cost. This is a different sweet spot on the cost
| benefit curve.
| aspaceman wrote:
| High performing code doesn't have to be hard to reason about.
|
| You wouldn't say the plan for a safer building is harder to
| read.
|
| People excusing their poorly written code as "highly-optimized"
| is the oldest cop-out we've got as programmers.
| lumost wrote:
| I generally only observe value in application code optimizing
| for correct data structures/batching/data parallelism. Once
| you're getting into fine tuning the batch size/SimD ops for a
| particular platform you are quickly into the realm of
| diminished ROI due to fragility against changing hardware,
| application usage patterns, and diminished returns where
| dropping an op from 1 microsecond to 10 nanoseconds just
| doesn't matter as much.
|
| There are cases at the tail where this will make or break a
| project, but those are pretty rare.
| desperate wrote:
| Slightly off topic but I just realized that not only does
| optimized code = less processor cycles but less cycles = less
| power used.
|
| This brings two rhetorical questions, How much more effective
| compute would we have if all code running today were perfectly
| optimized, and how much less energy would be needed under the
| same circumstances?
|
| The computer gains could only be used in networks where unused
| compute can be scheduled / sold to other tasks. The energy
| gains could only be reaped if processors had proper low power
| modes they could fall into or some other method of
| conservation.
| dahart wrote:
| > All the languages mentioned in the article are still around
| because it doesn't matter how performant 99% of code is.
|
| It's worth challenging this assumption and asking whether it's
| really true, or whether this belief is old and outdated like
| the old languages. I'm certain that providers of compute
| infrastructure like Amazon, Microsoft, Google and others
| completely disagree that performance doesn't matter. Letting
| slow code run literally costs them money because it prevents
| multiple people from being able to use the same hardware
| resource. Even on your own machine, slow processes hog
| resources that other processes could use. The reason my laptop
| fans keep spinning up in Windows while my machine is basically
| idle is due to hundreds of processes written by people who
| erroneously believe the performance of 99% of the code doesn't
| matter. We're also at a point where we've started to measure
| the energy efficiency and environmental impact of wasting
| compute cycles. So, performance _always_ matters.
|
| > For the 1%, we can think about cache misses [...] but that
| has the enormous downside of making code hard to reason about.
|
| You're agreeing with the author here. Maybe you missed the
| explicit point in the in the article that is calling for a
| language that makes basic cache awareness a first class
| citizen, precisely so that it's easier to reason about?
|
| "This is exactly the type of problem that should be solved via
| the features of our programming languages, since writing these
| custom sorting functions is very tedious. What if a programming
| language would provide us with a structure that would act like
| an array of structs, but internally it would really behave like
| a struct of arrays? We could program in the typical object
| oriented way that is convenient for humans, while still
| enjoying in great performance due to playing nice with the
| hardware."
|
| > You are also optimizing for today's hardware, which means
| that it might not run that great on new hardware in 20 years.
|
| For the last 20 years, the need to be cache-aware has increased
| in importance. For the next 20 years, the gap between memory
| and compute will get larger still. There's no scenario on the
| horizon where we don't have to become more memory conscious as
| programmers or use more memory conscious languages and tools.
| There aren't many performance tricks and concepts that worked
| 20 years ago and don't work today. Using a structure of arrays
| (SoA) instead of an array of structures (AoS) has long been and
| will continue to be a good idea.
| kaba0 wrote:
| Feel free to profile a high-profile application. It is almost
| always a few hotspots that "suck".
|
| There are rare cases where there is no one point that is
| slow, that's a bad situation to find oneself in -- it usually
| means that the base architecture/abstraction sucks and needs
| a big refactor.
|
| But even in so low-level code as the linux kernel, you see
| the use of linked lists all the time. Because it simply
| doesn't matter if you are not in a hot loop and iterate
| slowly over 3 elements.
| dahart wrote:
| FWIW, I profile intensive applications all the time, it's
| my job. You're right about the existence of hot spots, but
| that's not particularly relevant to my point or the
| author's. Just because one spot is hot doesn't mean you
| should rationalize using lazy or bad-practices programming
| on the rest. Just because you don't see a bottleneck
| somewhere when you profile doesn't mean it'll stay that way
| when other processes want the same resources at the same
| time. Knuth's famous quote was not intended to be a license
| to ignore using best practices in general for non-hot-spot
| code.
|
| Use of linked lists in the linux kernel is also not very
| relevant, and somewhat of a red herring. Kernel-level (as
| well as games & embedded programming) linked lists are
| frequently allocated and used very differently from typical
| high level language linked lists. Scripting languages are
| allocating individual linked list nodes by default. Linux
| uses linked lists for the memory allocator itself, for
| example, and allocates nodes at a lower level in blocks.
| There are no particularly good alternatives, and no great
| ways for the allocator itself to be cache aware, though
| that is ongoing research. The main perf problem with using
| linked lists in high level languages is not the fact that
| they cause cache-incoherent access, it's worse, it's the
| fact that they allocate and deallocate memory constantly
| which is much much slower. This is one reason why linked
| lists don't get used as much as arrays in high level
| languages, when arrays are a reasonable alternative.
|
| Anyway, it's somewhat of a straw man to bring up linked
| lists when the author's valid point was SoAs are better
| than AoSs from a cache perspective, and that our languages
| aren't yet doing much to help us code that way, but they
| could in the future if we decide to want them to.
| kaba0 wrote:
| I apologize for my cocky reply.
|
| I just meant to point out that SoA vs AoS or an even
| slower data structure is not necessarily relevant for all
| programs.
|
| > The main perf problem with using linked lists in high
| level languages is not the fact that they cause cache-
| incoherent access, it's worse, it's the fact that they
| allocate and deallocate memory constantly which is much
| much slower.
|
| This is not true though; at least the JVM most definitely
| doesn't do that, and I'm sure other state-of-the-art GCs
| neither, like V8.
|
| First of all, allocation is basically free, much faster
| than malloc and friends. It's only a pointer bump. And
| the cost of deallocation is amortized/may not even
| happen. With plenty of memory available, GC doesn't
| really happen, and for long-lived objects in case of a
| generational GC, they may not necessarily get checked at
| all after a time.
|
| So the usual linked list gets appended a few times is
| close to optimal, iteration has the same cost as in lower
| level languages with pointer chasing (usually a bit more
| because objects usually have headers that take quite some
| space, less of them potentially fitting into cache). It
| even has some advantage in that a modern compacting GC
| may move the objects close to each other.
| noobermin wrote:
| This attitude is very common and has clearly degraded software
| development particularly for users, and like most issues
| afflicting modern software it's not perceived by developers so
| it "doesn't exist" to them. As the top reply to this mentioned,
| anyone can easily tell how much slower software is today even
| when computers and devices are so fast now! All in the name of
| "making code easy to reason about."
|
| I have news for you, _all code is difficult to reason about_ at
| some level. Sure, certain abstracts can be easier to reason
| about than others if you take certain examples that I can
| generally argue are reductionist, but any reasonably well
| developed piece of code (read, actual useful code) will require
| a certain level of sophistication and complexity that will
| eventually make it difficult to reason about.
|
| I know this is a reach and a bit of a red-herring, but
| developers really really need to remember the allergy to "code
| being difficult to reason about" is a sometimes a useful
| intuition or heuristic to use, the heuristic being "easier to
| reason about is better," but it really is a _heuristic_ at the
| end of the day. Abuse of this heuristic is often a motive
| behind all sorts of detrimental behaviors in tech, reinventing
| the wheel and NIH (I didn 't write, it, so it's difficult to
| reason about!), rewrites from scratch (our "technical debt" is
| now hard to reason about, let's rewrite from scratch!) and the
| problem OP refers to (a solution that maps to the machine
| better is hard to reason about, let's use this less efficient
| solution instead!). And a big problem here is that these issues
| are often not issues to hackers because it just means more
| programming for them which they love! It just hurts users and
| customers.
| reader_mode wrote:
| >As the top reply to this mentioned, anyone can easily tell
| how much slower software is today even when computers and
| devices are so fast now! All in the name of "making code easy
| to reason about."
|
| Yeah - it also doesn't crash my PC when an app segfaults,
| hell even video driver failing these days won't bring my
| system down. I don't need to restart my PC when I plug in a
| device. That's not really compatible with your childhood DOS
| games that enter kernel mode and talk to HW directly - you
| pay for abstractions - and it's a good tradeoff.
|
| I can also have files with more than 5 character file names,
| and my FS can revert file changes and create snapshots.
|
| These "good old days" people are just unrealistic, VIM is
| fast even today - but I prefer having a graphical
| representation of file tree with fancy icons and search. But
| that's just me and 99% of people out there.
|
| Performance is a feature - and it's not a feature most people
| rank high untill it becomes an issue. As much as a minority
| of people here are crying about electron and HTML - a huge
| number of developers use VSCode daily and enjoy the
| approachability of a JS/DOM plugin ecosystem. VIM is free and
| available for decades now, and it has such amazing UX that
| the one of the most upvoted questions on SO was how to exit
| from it...
|
| Slack is dog slow - and IRC has been freely available for
| decades, most people here use Slack daily. Who uses IRC
| anymore ?
|
| So keep using your Emacs integrated window manager and stop
| bothering the rest of us :D
| noobermin wrote:
| My point really isn't about luddite vs. anti-luddite, I'm
| talking about performance and how it affects users. Also,
| you're right that less performant or less elegant tools win
| out sometimes (like the good old mac vs windows debates
| before macos and apple in general made a come back) and
| that is due to a variety of reasons. That's fine, life is
| complicated and many factors affect who wins in life, it
| doesn't mean we can't still ask for better things though.
|
| Still I don't know what bearing of all this has on the
| point I made above.
| 8fGTBjZxBcHq wrote:
| I think other users in here are more on the right track about
| where the blame for this lies.
|
| As you alluded to, a lot of programmers are happy to tweak
| and refine for ages so it's probably not that.
|
| Is it just that programmers are lazy or need to care more or
| be better educated about optimization?
|
| Honestly I really doubt it. I fully agree with the other
| takes that companies have incentives to ship working software
| quickly. Features sell subscriptions not performance. If it
| comes down to choosing between functionality and performance
| leaders will ship the slow feature every time.
|
| I'm not even sure they're necessarily wrong most of the time.
| If you know where to look the internet is a graveyard of
| carefully engineered products that were beat to market and
| obliterated by three people with a shitty electron app.
| noobermin wrote:
| You're probably right honestly that bean counters are
| mostly to blame. I will say the attitude of being afraid of
| difficult code is a problem in modern IT (IT of any
| generation really) and doesn't help but companies not
| caring about users being the primary cause of bad,
| unperformat code is the correct take. Conversely then, the
| handwaving of the OP comment is wrong too, so if we limit
| the discussion of what one ought to do, I still stand by my
| point.
| bobcostas55 wrote:
| This is exactly how we ended up with someone charging you
| $50/month to run a browser on a supercomputer.
| esailija wrote:
| I disagree. Almost all software I use these days is absurdly
| slower than it should be and probably a massive drain on
| collective productivity. I have ssd, fast internet, 64gb ddr4
| etc, almost everything I do should be impercetibly instant but
| instead takes seconds and even minutes.
|
| Yeah of course most of that slowness is not even because of
| ignorance of the low level stuff but complete disregard of
| performance aspect of software. If it's even slightly more
| convenient for a programmer to do something that is very stupid
| performance wise, they will do it.
|
| Sure developer time costs, but this cannot actually be the
| reason. Because tooling is also ridiculously slow in exactly
| same way and wastes countless of hours of that costly developer
| time.
| 29athrowaway wrote:
| Shit is slower than it was in the 90s.
|
| Spotify is slower than Winamp, Slack is slower than IRC,
| Webmail is slower than my e-mail client, VS Code is slower
| than Visual C++ 6.0.
| uhhhhhhhhhhhhhh wrote:
| VLC has pretty good startup time. VScode plugins
| (generally) dont take down the whole program or BSOD any
| more. I do feel that its not as snappy, which is sometimes
| physically unpleasant, but it is possible that we now wait
| less because we're not rebooting or ctrl+alt+del'ing our
| way out of some nullptr deref
| krapp wrote:
| >Spotify is slower than Winamp, Slack is slower than IRC,
| Webmail is slower than my e-mail client, VS Code is slower
| than Visual C++ 6.0.
|
| All of which are Electron apps, apart from "Webmail" which
| is, well, what one might call a "distributed Electron app"
| (a website.)
|
| Maybe, just maybe, Electron is the problem.
| jupp0r wrote:
| Spotify is not an Electron app, they are using Chromium
| Embedded Framework.
| reynhaim wrote:
| Spotify runs on all of my devices. In the morning I can ask
| my smart speaker to play songs that Spotify presumes I
| like, continue from my car with a voice command, see what
| my friends are playing or play their playlists. I'll gladly
| wait for the ~5 seconds it takes for an app to start if it
| delivers all this.
|
| Of course IRC is much faster, it's also few orders of
| magnitude simpler than Slack. For work related
| communications I prefer Slack to IRC, but for chatting with
| friends IRC does just fine. One simple protocol vs hundreds
| of APIs that provide extremely rich content. Once again,
| Slack takes few seconds to launch on a modern machine, it's
| not that bad considering it does so much.
|
| Yeah shit is slower, shit is also way more connected and
| complex than in the 90s.
| corysama wrote:
| People forget that Winamp was instant on a 60Mhz machine
| with a spinning rust drive. It's difficult to buy a
| computer that slow these days. Modern CPUs modulate their
| speeds by far more than that as part of their moment by
| moment power management!
| jayd16 wrote:
| Winamp didn't stream all music in existence over the
| internet. How is it really comparable?
| kleiba wrote:
| It's comparable only on an abstract level - mostly
| because there's hardly anybody who really wants/needs
| "all music in existence over the internet", most people
| just want to "listen to music that I like".
|
| On that level, Winamp did the job just fine.
| 8fGTBjZxBcHq wrote:
| It really did not for me by that standard. I have access
| to orders of magnitude more music now that I know I like
| than I did during the Winamp days and I was a pretty
| early and savvy user back then so I had more access than
| a lot of people.
|
| If we're trading music player performance for the massive
| increase in availability I'll make that trade.
| jayd16 wrote:
| Is that why everyone uses a streaming service and no one
| collects music any more?
| TeMPOraL wrote:
| Because streaming music is a trivial task,
| computationally-wise. It could have had some performance
| impact _in 2000_ , but we're in 2021 now.
|
| And nothing else related to the functional differences
| between Spotify and WinAMP can explain why Spotify is
| slow. Done properly, it should feel snappier than WinAMP
| felt in 2000.
| tonyarkles wrote:
| Lol arguably Winamp + Napster did :D
|
| But really, 99% of the value-add of Spotify over Winamp
| is stuff that happens server-side. Your Spotify client
| doesn't have a database of all the songs, nor the ML
| models for computing recommendations. As far as playing
| streaming music goes, Winamp would happily play an m3u
| (mmmm soma.fm).
|
| Don't get me wrong, the discovery and search features in
| Spotify have brought me a ton of value. But wow is the
| client resource usage dramatically disproportionate for
| the added functionality
| TeMPOraL wrote:
| And the kicker is, the music client is a completely
| separate concern from a song database, or a
| recommendation engine.
|
| This is actually a point in favor of directly comparing
| WinAMP and Spotify - the important "value-add" bits of
| Spotify all happen server side, so they should have
| exactly _zero_ impact on client performance.
| Firehawke wrote:
| It had Shoutcast.
| jayd16 wrote:
| Was Shoutcast all that fast? It didn't seem nearly as
| rock solid as the rest of winamp but I guess it could
| have been my internet at the time.
| goodpoint wrote:
| Yes, it was instant.
| 29athrowaway wrote:
| You could go to the shoutcast website, get the shoutcast
| links for the radios you were interested in, then make a
| playlist that could be dealt with just like any other
| playlist.
|
| And it was almost instant.
| fiddlerwoaroof wrote:
| Related: when MacBooks switched to SSDs, the performance
| of my old spinning-disk MBP dropped noticeably basically
| overnight.
| Narishma wrote:
| Same thing on Windows. It became incredibly slow on HDDs
| when SSDs started to become popular.
| cassepipe wrote:
| Having an Hdd running at 100% on Windows 8, slowing
| everything down, is actually what made me switch to Linux
| Mint. Never looked back.
| jdsully wrote:
| The amount of effort involved in making sure reads were
| sequential back in the HDD days was immense. It's not
| surprising people dropped that as soon as possible.
| selfhoster11 wrote:
| When I first bought an SSD and noticed the crazy speed-
| up, my next thought was that developers will get used to
| it as the "new normal" and stop caring about IOPS which
| would slow down SSDs (ruining my blink-of-an-eye super-
| speed fun) and make HDDs near-unusable. Two to four years
| later, here we were on Windows, and to some extent, on
| Linux too.
| 29athrowaway wrote:
| Winamp 2 was instant. Winamp 3 and 5 were slow.
| astrange wrote:
| Slack is definitely faster than my IRC client since it's
| not running over ssh to a host. You can't actually run IRC
| locally or you'll miss all the chat from when you're not
| signed in.
| Shikadi wrote:
| You can use a bouncer so that's not true
| fiddlerwoaroof wrote:
| Yeah, this is the weirdest thing to me: Windows NT 3.51 on
| a Pentium was much snappier perceptibly than even my new M1
| MBP (developers are already expanding to the new
| performance envelope).
| haberman wrote:
| Dan Luu has done some measurements around this:
|
| https://danluu.com/input-lag/
|
| https://danluu.com/term-latency/
| 29athrowaway wrote:
| Similarly, SNES games were more responsive than modern
| games and have zero loading screens.
| kaba0 wrote:
| And didn't render millions of polygons at 60+ fps, while
| having many GBs of assets like textures, so I don't see
| how is it relevant.
|
| One could write those programs in a truly inefficient
| manner in a not too performant language and it would run
| without problem. Today's computers are really fast.
| fiddlerwoaroof wrote:
| The problem is that, instead of the speed of modern
| computers translating to perceptible performance
| improvements for normal users, it translates to
| applications that use more resources.
|
| Whether or not the new features make up for the general
| perceptual slowness of modern computers is a somewhat
| open question.
| kaba0 wrote:
| I would agree with you, but in terms of games it is
| simply not a great example. It may be questionable why
| would we want photorealistic games when 2D, visible
| pixels are good enough, but games are not known for being
| inefficient.
| XorNot wrote:
| Windows Server 2019 has a much snappier UI then Windows
| 10 I've found (I recently spent a ton of time automating
| installs for both and boy did that become noticeable).
|
| Then you look at the Windows 10 list of crapware and it
| becomes a lot clearer.
| Spooky23 wrote:
| Windows 10 is an abomination.
|
| It's incredible how much grief Windows Vista got in
| comparison.
| selfhoster11 wrote:
| Vista was a worse abomination on 2 GB of RAM. It's likely
| that if I installed a build of Windows 10 from 2 years
| ago on the machine in question, it would be faster than a
| greased lightning in comparison.
| stinos wrote:
| _Then you look at the Windows 10 list of crapware and it
| becomes a lot clearer._
|
| Is there an actual causal relationship? And which
| crapware specifically? Asking because I only have access
| to a bunch of Windows 10 Pro machines which already don't
| seem to have most of crapware on there (i.e. often I see
| threads here where people complain about all kinds of ads
| and other things I never even knew existed) and the rest
| disabled (as far as I can tell) and still it's less snapy
| than this one Windows Server instance on comparable
| machine. (and sadly no single modern OS feels as snappy
| as even Windows XP SP2 for a similar level of
| functionality on not-so-recent hardware)
| freeone3000 wrote:
| The difference is the list of running background
| services. Kernel and drivers are the same, libraries are
| the same, but server doesn't have (ex) the AllJoyn Router
| Service for Smart Things Control, the 4G LTE Push
| Notifications Service, the WAP Push Message Service, the
| Fax Service, Windows Image Acquisition, and so on. Candy
| Crush doesn't run on startup, these do.
| User23 wrote:
| The subjectively fastest, which is to say the most
| responsive or lowest input latency, computer I've ever used
| was a Macintosh 512ke upgraded to 2.5 megabytes of memory,
| running the system software and apps off of a 1.5 megabyte
| RAM-disk. When you double-clicked, say, MacPaint in the
| Finder, it was loaded before you finished the physical
| mouseup. This was when that hardware and software was still
| current.
| kaba0 wrote:
| I disagree that everything would be slow. Yeah, there are
| websites with so many JS bloat that they can make the fans
| start up in my laptop, but otherwise, your computer does a
| shitton of things, most of which is essential complexity.
|
| To-the-point software is actually really fast, like gcc/clang
| does tons of optimizations compared to what compilers did a
| few decades ago, and I doubt you could realistically optimize
| it by a significant percentage in the general case. Same for
| databases. Browsers themselves are also pushing the
| boundaries of hardware, they can display static HTML/CSS
| ridiculously fast even though it is not ideal for layouting.
| Pretty much only the top of the abstractions "suck"
| sometimes, but there are really fast JS apps as well.
| WrtCdEvrydy wrote:
| > Sure developer time costs, but this cannot actually be the
| reason.
|
| It totally is because we've abstracted the cost of software
| engineering unto the hardware.
|
| Back in the 90s, people had to basically beg for memory when
| writing software while modern development is built on the
| theory of getting the fastest delivery speed at the cost of
| performance. We outsource the cost of software unto millions
| of customer's hardware and it's never coming back because
| going to market quick makes you money.
| Olreich wrote:
| The point stands anyway. Why are all my dev tools super
| slow? If my time is important, shouldn't those be fast and
| heavily optimized? And don't try to tell me that 4 billion
| cycles a second across 16 cores operating on in-memory
| contiguous values isn't enough to update a watch window
| more than 1 time per second.
| rbanffy wrote:
| If my tools are faster than me, there's no point in
| making them even faster. In fact, I'd trade some of that
| speed for a smaller memory footprint.
|
| In your example of the watch window, updating it at more
| than 30 fps is probably overkill.
|
| One very common mistake is optimising the wrong thing. My
| advice is always the same: first run a profiler with a
| real workload (absent that, eu it over your automated
| tests), then look at what you need to optimise for speed.
| All the rest should be optimised for readability.
|
| In the article's case, if counting warriors is so
| important, keeping a counter at the AntColony class
| that's updated every time you write to an Ant would be
| the obvious solution.
| WrtCdEvrydy wrote:
| > Why are all my dev tools super slow? If my time is
| important,
|
| Quantify important into how much you spend on your
| tooling.
|
| Aside from some key bits of development software at the
| IC / board layout, almost everything a software developer
| needs is free, open source or cheap. Noone pays $1000 for
| an IDE anymore.
| harry8 wrote:
| > Noone pays $1000 for an IDE anymore.
|
| How many minutes per developer per day before that level
| of expenditure pays for itself inside 2-3 months?
|
| Who cares? The premise is false, you can't buy it. There
| is no "fast tools" you pay for. Every decent shop got
| their devs an SSD at the first available opportunity
| because it paid for itself really quickly, cost you money
| _not_ to buy them. The very definition of a false economy
| was "no the devs aren't getting new shiny sportsmode SSDs
| for their boxes."
|
| We all use gcc and clang (for relevant languages), for
| example because everything else you might pay for sucks a
| lot harder. Whatever the ideology you claim or deny.
| Intel literally damaged their customers' products to try
| to make themselves look better than AMD with theirs!
| They're a non-starter after showing themselves willing to
| do that but if you must, yes, their compilers still suck
| exclusively on intel more than gcc. How is microsoft's
| compilers standards chasing going? In the 21st century
| yet or /still/ not? (Oh come one, we're mostly there but
| for ... yeah). Compiling template heavy C++? Does
| developer time matter?
|
| (But just quietly, terminals & cmdline tools feel a lot
| more productive than IDEs to me and I use both in
| differing circumstances).
| vbezhenar wrote:
| I don't have a hard math here. But here's some example:
| JRebel. It allows for instant class reloading in JVM. It
| increases one's productivity tremendously. And it's
| expensive as hell. They used to have free version, they
| don't have it anymore. I can only judge that they're
| struggling and forced to squeeze all they can, because
| developers will prefer free restricted version over paid
| full-featured version. I know, I do, I don't value my own
| time and I'll never pay more than $100/year for a tool
| (and hopefully much less), I already trying hard to
| justify Idea subscription, because it seems that Idea CE
| contains almost everything I need.
|
| If developers would love to spend thousands of bucks here
| and there, paid developer tools industry would flourish.
| But I think that it's even degraded compared to the past.
| I remember paid Delphi IDE and I remember paid components
| and that was a thing back then. I can't remember any
| popular paid React component right now, despite the fact
| that much more people are using React compared to Delphi.
| einpoklum wrote:
| You're both right - but about different kinds of software.
|
| I believe the GP post was thinking an application which has
| some heavy computational kernel to it, where most of the
| processor time (and other resources) are spent - with a lot
| of other code for UI, configuration, some parsing etc.
|
| And you seem to be talking about everyday desktop
| applications: Browser, mail client, word processor, instant
| messaging client, and maybe even your IDE as a programmer.
| Those kind of apps don't have that kernel of hard
| computational work - their work is much more diffuse.
| jupp0r wrote:
| I spent years optimizing physics simulations from different
| domains (weather, fluid dynamics, all sorts of stuff). Even
| there you have to carefully pick the parts you want to
| optimize to get the best outcome for the resources you
| invest in optimization. It's completely infeasible to make
| everything crazy fast with a blanket approach.
| klyrs wrote:
| You're speaking directly past your parent's point. I've
| spent years optimizing physics simulations, more abstract
| discrete math stuff, etc. myself. There, _especially
| there_ , it's usually pretty easy to identify what should
| be the hot loop, by eye. Easier with a profiler if the
| project gets big. In scientific computing, it's very
| common to find that 99% of the time is spent on a scant
| few lines of code. Unless somebody bungs up the I/O and
| you end up spending 99% of the time there instead, but I
| digress.
|
| But a whole OS, with a browser, network stack, dozens of
| applcations, etc., it's pretty common to find hundreds or
| thousands of things which are all more or less equally
| sucking performance. So on one hand, it's a _much_ harder
| problem. On the other hand, front-end people have this
| attitude that performance doesn 't matter because
| hardware is fast enough and it's better to write in the
| most abstract language possible because changing diapers
| makes your hands smell gross.
| quadcore wrote:
| I dont know if you've noticed, but most programmers actually
| do care about performances. They just are bad at it. Software
| are slow precisely because programmers dont get it:
| optimising all the code will make slow software. They spend
| their budget optimising 99% of the code and end up with no
| more money to make that last 1% fast. For real (game engine
| programmer speaking). Plus optimising all the code make all
| the code unreadable which drains even more money from budget.
| Just as OP said.
| astrange wrote:
| Usually simpler code is better. I used to see a lot of
| mistaken "high performance" code where someone has unrolled
| all of the loops because they think unreadable code goes
| faster, though maybe people have gotten better about this?
| (OpenSSL is an example here.)
|
| Unfortunately not all kinds of slowness only happen in
| hotspots. This is true for CPU cycles, but if an occasional
| task uses all memory, it's going to mess up everything
| downstream as well.
| selfhoster11 wrote:
| > I dont know if you've noticed, but most programmers
| actually do care about performances. They just are bad at
| it.
|
| I disagree. Programmers may care about performance, but
| project managers certainly don't and they always have the
| final say.
| tonyarkles wrote:
| I'm with both of you and I don't think what you two are
| saying is mutually exclusive. I'm kind of a performance
| nut (often working with teams, I'm the guy that
| introduces everyone to a profiler) and, in my N=small
| experience, PMs are opposed to spending time on
| performance optimization precisely because most people
| are bad at it and it takes an undefined amount of time
| for unknown performance gains.
|
| When I take an hour to profile and analyze some request
| and say "I think we can cut this thing's response time by
| 80% by optimizing $X, which will result in an overall
| page load time reduction of 3s", that's way easier to
| sell than "I want to figure out why this page load is so
| slow" and then reporting back a week later with " _shrug_
| I optimized 5 things and it didn't get faster. Dunno."
| goatinaboat wrote:
| _Programmers may care about performance, but project
| managers certainly don 't and they always have the final
| say_
|
| LOL no. Offer a dev choice between a framework that is
| trending on Twitter, vs one that is 100x faster but
| "legacy" (meaning more than a year old) and she'll choose
| the first one every time, and the project manager won't
| know the difference, or care if he did.
| jupp0r wrote:
| Do you use browsers? Browser makers spend billions of dollars
| in engineering time making them "fast", with great results.
| kleiba wrote:
| https://twitter.com/cmuratori/status/1369149371489513474
| toss1 wrote:
| >"Browsers... "fast", with great results."
|
| Are you joking?
|
| I have seen progressively worse performance from even the
| best browsers, and page load times that should be instant
| often literally take minutes or never load unless I
| completely kill the browser process and return. The
| slowdown is nearly inexorable, with occasional improvements
| in some versions before resuming the dismal trend.
|
| Simple word processing and spreadsheets are so laggy on
| keystrokes as to be almost unusable, and by unusable,
| unresponsive on a level hundreds of times worse than
| DECADES ago, on computers orders of magnitude less
| powerful. Simple cursor movements are so laggy that I must
| set aside my train of thought to attend to the tool.
|
| And this is on a very solid CAD-level computer, FIOS
| connection, etc.
|
| It is disgusting and unforgivable. I quit software career
| 15 years ago for a new industry in no small part because I
| saw this trend of ever more complex "tools" & "frameworks",
| etc. creating a situation of building castles on shifting
| sands, with serious declines in the ability to reason about
| debugging, performance, or security. It is only worse now,
| probably exponentially. Worse yet, it seems to have yielded
| no perceptable "programmer productivity".
|
| It is one thing to architect and program to take advantage
| of upcoming advances in hardware. It is quite another thing
| to ignore it and assume that the hardware builders will
| save you from the bloatware that you foist on the world
| without a serious thought.
| jupp0r wrote:
| Maybe you should visit better websites? Try
| http://www.quakejs.com/
| vbezhenar wrote:
| I'm using very underpowered laptop (Dell 3410) and web is
| extremely fast for me. There's some serious problem in
| your OS or your network if websites take minutes to load.
| Dreadful gmail or youtube loads in 1-2 seconds for me and
| then works instantly. Simpler websites like HN loads in a
| fraction of second.
| pwdisswordfish8 wrote:
| > Are you joking? I have seen progressively worse
| performance from even the best browsers
|
| Where is the evidence that you're seeing poor performance
| from the browser itself and that the source of the
| problem does not lie in the difference between what the
| server is sending down the tubes today compared to what
| it was sending 10 years ago?
| toss1 wrote:
| I agree that what the server is 'sending down the tubes'
| is a huge part of the problem.
|
| But this is the root cause we're discussing - programmers
| selecting tools for their convenience (and worse yet,
| cool factor), instead of FIRST considering the
| responsiveness of the system as they design and code.
|
| Optimization as an afterthought is about as good as
| security as an afterthought - anything from a complete
| waste of time to a disaster.
|
| There are indeed pages that load like lightning, so it
| can be done (e.g., HN takes about 1.5sec to create a new
| window and load, so not exactly lightning, but usable),
| but many are horrible, and clearly due to bad
| programming.
|
| For starters, when I see a page that loads code from 25
| different sites that need NoScript privs to even display,
| that alone is pretty questionable - license and manage
| your own damn code (for the sake of minimizing dependency
| alone!). Twitter is particularly egregious in the last
| year or so, a new page taking 10sec-?? to load, and the
| LAST thing that loads is the list of posts -- the same
| load times it would feel so much more responsive if that
| was the first to load, and the other navigation, news,
| etc. panels loaded later while I was reading. That is a
| many bad programming choices.
| kaba0 wrote:
| But the browser itself is plenty fast. Just open a huge
| static HTML file, or some well-optimized JS one like some
| simulation.
|
| The problem is some websites
| sudosysgen wrote:
| Browsers are fast, but the content they render is very
| slow.
| bogdanoff_2 wrote:
| Reminds me of the "paradox" that making technology more
| energy-efficient has the effect of increasing overall
| energy use.
| CalChris wrote:
| Jevon's paradox
|
| https://en.m.wikipedia.org/wiki/Jevons_paradox#Khazzoom-
| Broo...
| fiddlerwoaroof wrote:
| These are related: the faster the browser and the typical
| internet connection is, the bigger sites will be (absent
| some force to prevent developers from spending the
| entirety of the new performance budget.)
| klyrs wrote:
| Do you use websites? Because developers unthinkingly pull
| in billions of dependencies, making them "features", with
| horrific results.
|
| And fast is one aspect of performance. What's the memory
| footprint?
| Silhouette wrote:
| And yet developers working on front-end code to run in
| those browsers are waiting seconds for their linter or test
| suite or transpiler to process a few thousand lines of JS
| code, on a modern PC. Not a few million lines, a few
| _thousand_. That is orders of magnitude slower than it
| should be, and it 's a cost that hits huge numbers of
| developers many times every day. Quite often, that awful
| performance is because the slow tools are written in a
| certain language also used in those "fast" browsers, and
| far too many developers are still giving this a pass
| because they incorrectly assume some magical runtime and
| JIT will make up for using a language that was never
| designed for high performance work and writing large,
| complicated programs.
| pwdisswordfish8 wrote:
| The problem with the NPM ecosystem is really dire because
| know-nothing programmers who write JS plug in to that
| ecosystem and write bad code, and then the know-nothing
| programmers who don't write JS come along, too, and say
| look how bad JS is.
|
| JS is plenty fast and can be used for "large, complicated
| programs"[1] just fine. The problem with most JS written
| today is in programmer practices--the way that the
| community associated with NodeJS pushes one other to
| write code (which is, ironically, not even a good fit for
| the JavaScript language). It turns out that _how_ you
| write code actually matters (which is the central point
| of the article here to begin with...)
|
| 1. https://web.archive.org/web/20070127104743/http://www.
| spread...
| jayd16 wrote:
| I think you missed the point. Parent is saying that without
| compiler optimization everything would be even slower.
| goatinaboat wrote:
| _Almost all software I use these days is absurdly slower than
| it should be and probably a massive drain on collective
| productivity_
|
| Productivity, energy usage, e-waste in landfill as people are
| forced to upgrade in order to do simple tasks with the
| current software... The world is paying a high price for
| programmer laziness.
| city41 wrote:
| Is it programmer laziness, or insane demand for software
| due to many factors, mostly profit?
| goatinaboat wrote:
| _Is it programmer laziness, or insane demand for software
| due to many factors, mostly profit?_
|
| There's insane demand for games, and those programmers
| are able to optimise their code, insane demand for
| trading software and those programmers are able to
| optimise their code. But webdevs just don't care. I look
| at pages now that do nothing more in functionality than
| the same app would have done 20 years ago, yet they are
| slower now, and I have literally 1000x more CPU power.
| It's insane.
| city41 wrote:
| Optimized games have a well known impact on sales. I'd
| assume the same with trading software. Putting out a
| blanket statement that web devs are lazy is just silly.
| Developers rarely have much say in how much time they can
| dedicate to features and the app as a whole, that usually
| boils down to PMs and management. If the org as a whole
| doesn't value performance or doesn't think it matters,
| then that will get reflected in the product regardless of
| what the developers actually want.
| 8note wrote:
| Most performance concerns that I run into are related to
| advertisements that need to load.
|
| The business model isn't right for performant experiences
| GuB-42 wrote:
| Faster = more ad revenue. If your app/site is too slow,
| people will stop using it and won't see your ads. Also,
| search engines will detect that and downrank you, meaning
| even less ad impressions. It has been studied many times,
| and yet, developers don't do it.
|
| Simply, performance is expensive and for most businesses,
| the additional revenue is not worth the additional expense.
| toast0 wrote:
| > I disagree. Almost all software I use these days is
| absurdly slower than it should be and probably a massive
| drain on collective productivity.
|
| This is absolutely true, but the problem is usually a lack of
| due care about performance at higher levels, rather than low
| level optimization. Things are usually slow because they do
| entirely too much of the wrong things, than because nobody
| optimized the things, or the wrong registers were used or
| what not.
| edenhyacinth wrote:
| The article presents the case that writing code with a
| specific style - OOP compared to Data Oriented - is
| responsible for slowing down performance. That code
| architecture is primarily the cause, regardless of the
| actual implementation, because some architectures will
| never work well with the compiler.
|
| I get that like the major parts of picking algorithms that
| are N over N^2 is always going to be more important, but it
| doesn't seem like the article is disagreeing with you at
| all, just another area where people need to consider their
| decisions.
| kaba0 wrote:
| But only a strict subset of problems are heavily data
| oriented. For your 3 button GUI app whether you use a
| tightly packed AoS, even SoA doesn't matter over a slow
| linked list of pointers for that 3 element list
| implementation doesn't matter.
|
| And OOP is simply not contradictory to DOD.
| toast0 wrote:
| (Agreeing) The 3 button GUI app is much more likely to
| be/feel slow because the UI framework used makes it
| difficult to get the widgets on the screen quickly than
| because of data processing.
|
| Maybe it insists on a deep heirarchy of widget
| inheritence as many do. Maybe the framework wants to load
| and configure all the possible widgets, even though the
| application only uses a few. Maybe theres a bunch of
| images loaded from disk even though they're not used.
| Maybe it's just the _fantastic_ default compositing
| system that puts everything a frame behind adding up with
| everything else.
|
| There certainly are applications where data structures
| matter, and time spent processing data is significant to
| user experience, but that's not why the whole computing
| experience feels slower (although maybe prettier) than 25
| years ago, despite capability being so much more for most
| things (as pointed out in the article, ram is better than
| years ago, and we certainly have more of it, but ram
| access takes a lot more cpu cycles now)
| kaba0 wrote:
| I am torn on the issue. On one hand, there is definitely
| useless technical debt, over-abstraction, no longer
| useful features slowing down our programs. But at the
| other hand, we are actually handling every language on
| Earth properly (or at least we very well should already),
| instead of just saying that yeah ascii is good enough,
| somewhat care about accessibility though far from enough,
| etc.
|
| Also, actual native GUI apps are quite fast imo. Only
| electron apps are slow, but I really do think that it is
| mostly due to the web having a wrong abstraction for
| GUIs, not due to other reasons (of course there are
| shitty web apps, as well as shitty programs everywhere
| else)
| dnautics wrote:
| > So far, the only programming language I know of that supports
| this type of crazy data transformations is JAI
|
| I believe zig supports this too, and it's very encouraging that
| the creator has recently done a refactoring of the language
| parser taking a data-oriented approach, so that has surfaced pain
| points to be repaired for folks who would like to do things that
| way.
| smasher164 wrote:
| Impedance mismatch. Languages don't give the compiler enough
| semantic information to work with, and some require heavy
| runtimes. Compilers, on top of having to infer semantic
| information about the above, aren't able to sufficiently utilize
| the hardware, either due to constraints on the types of analyses
| done, or lack of control. The hardware exposes an API too high-
| level or too unfamiliar to existing compilers.
|
| IMO, the future is bright, because all these layers are being re-
| thought. However, phrasing this as a debate over programming
| paradigms is a red herring. If coupling data and behavior is a
| useful mental model, we ought to accommodate that while giving
| the compiler more to work with.
| selimnairb wrote:
| The data-oriented design approach is and has been standard
| practice for doing numerical simulations on High Performance
| Computing (HPC) environments. Still the problem of language
| support and syntactic sugar to make this style less error-prone
| and easier to do needs more work.
| selimnairb wrote:
| I think this approach applies even more so for GPU compute as
| it's the only sensible way to code these devices.
| tester756 wrote:
| >The hardware has improved, but the languages didn't
|
| I kinda disagree with author, because """languages""" improved
|
| Let's take a look at recent features in C#:
|
| Spans and CPU Intrinsics
___________________________________________________________________
(page generated 2021-05-03 23:03 UTC)